diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99573bd..1792244 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: - name: Setup python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Install create-dmg run: brew install create-dmg @@ -36,7 +36,7 @@ jobs: - name: Setup python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Build and package run: ./building/build_windows_x64.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d82821..75e3857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [6.1.0](https://github.com/leovan/SciHubEVA/compare/v6.1.0...v6.0.0) (2023-05-25) + +### Bug Fixes + +- Optimize fetching PDF URL when link is a relative path. + +### Features + +- Change to Material 3 theme. + ## [6.0.0](https://github.com/leovan/SciHubEVA/compare/v5.1.2...v6.0.0) (2022-10-02) ### Bug Fixes diff --git a/LICENSE b/LICENSE index a2fef58..a559253 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018-2022 Leo Van +Copyright (c) 2018-2023 范叶亮 Leo Van Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9fb6b70..d6cd833 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Sci-Hub EVA logo +# Sci-Hub EVA logo ![Release](https://img.shields.io/github/release/leovan/SciHubEVA.svg) ![License](https://img.shields.io/github/license/leovan/SciHubEVA.svg) ![Issues](https://img.shields.io/github/issues/leovan/SciHubEVA.svg) @@ -12,7 +12,7 @@ ## Usage -![Application macOS EN](docs/scihub-eva-application-macos-en.png) +![Application macOS EN](docs/scihub-eva-application-macos-en-light-theme.png) Click `OPEN` button to choose where to save the downloaded files. Click `SHOW` button will open the directory where you set. diff --git a/SciHubEVA.qrc b/SciHubEVA.qrc index 880cbfb..60393ff 100644 --- a/SciHubEVA.qrc +++ b/SciHubEVA.qrc @@ -6,17 +6,19 @@ ui/Preferences.qml ui/SciHubEVA.qml ui/elements/IconButton.qml + ui/elements/ItemDelegate.qml ui/elements/Message.qml + ui/elements/ScrollBar.qml images/SciHubEVA-background-dark.png images/SciHubEVA-background-light.png images/SciHubEVA-icon.png images/icons/box_important.svg images/icons/cancel.svg + images/icons/edit_file.svg images/icons/ethernet_on.svg images/icons/help.svg images/icons/info.svg images/icons/monitor.svg images/icons/settings.svg - images/icons/edit_file.svg diff --git a/app.py b/app.py index e31193f..7ba25dc 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,7 @@ import os import sys +import multiprocessing import scihub_eva.resources @@ -16,21 +17,34 @@ from scihub_eva.ui.scihub_eva import UISciHubEVA -if __name__ == '__main__': +def main(): + multiprocessing.freeze_support() + app_path = os.path.abspath(os.path.dirname(sys.argv[0])) - os.environ['QT_QUICK_CONTROLS_CONF'] = (PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix() + os.environ['QT_QUICK_CONTROLS_CONF'] = ( + PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix() if is_windows(): os.environ['QSG_RHI_BACKEND'] = 'opengl' + os.environ['QT_ENABLE_GLYPH_CACHE_WORKAROUND'] = '1' + os.environ['QML_USE_GLYPHCACHE_WORKAROUND'] = '1' + + if is_app_dark_theme(): + os.environ['QT_QUICK_CONTROLS_MATERIAL_BACKGROUND'] = '#3F3F3F' + else: + os.environ['QT_QUICK_CONTROLS_MATERIAL_BACKGROUND'] = '#FFFFFF' + QCoreApplication.setOrganizationName(ORGANIZATION_NAME) QCoreApplication.setOrganizationDomain(ORGANIZATION_DOMAIN) QCoreApplication.setApplicationName(APPLICATION_NAME) - app = QGuiApplication(sys.argv) + argv = [app_path, '--ignore-gpu-blacklist', '--enable-gpu-rasterization'] + app = QGuiApplication(argv) lang = Preferences.get_or_default(SYSTEM_LANGUAGE_KEY, SYSTEM_LANGUAGE) - lang_file_path = (I18N_DIR / 'SciHubEVA_{lang}.qm'.format(lang=lang)).resolve().as_posix() + lang_file_path = (I18N_DIR / 'SciHubEVA_{lang}.qm'.format( + lang=lang)).resolve().as_posix() if os.path.exists(lang_file_path): translator = QTranslator() @@ -40,5 +54,9 @@ icon_file_path = (IMAGES_DIR / 'SciHubEVA-icon.png').resolve().as_posix() app.setWindowIcon(QIcon(icon_file_path)) - eva = UISciHubEVA() + UISciHubEVA() sys.exit(app.exec()) + + +if __name__ == '__main__': + main() diff --git a/building/Windows/SciHubEVA-x64.iss b/building/Windows/SciHubEVA-x64.iss index 4547c15..d4f4071 100644 --- a/building/Windows/SciHubEVA-x64.iss +++ b/building/Windows/SciHubEVA-x64.iss @@ -1,5 +1,5 @@ #define MyAppName "Sci-Hub EVA" -#define MyAppVersion "6.0.0" +#define MyAppVersion "6.1.0" #define MyAppPublisher "Leo Van" #define MyAppURL "https://github.com/leovan/SciHubEVA" #define MyAppExeName "SciHubEVA.exe" diff --git a/building/Windows/SciHubEVA.win.version b/building/Windows/SciHubEVA.win.version index efaa17f..c43fe85 100644 --- a/building/Windows/SciHubEVA.win.version +++ b/building/Windows/SciHubEVA.win.version @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(6, 0, 0, 0), - prodvers=(6, 0, 0, 0), + filevers=(6, 1, 0, 0), + prodvers=(6, 1, 0, 0), mask=0x3f, flags=0x0, OS=0x4, @@ -16,12 +16,12 @@ VSVersionInfo( u'040904B0', [StringStruct(u'CompanyName', u'Leo Van'), StringStruct(u'FileDescription', u'Sci-Hub EVA'), - StringStruct(u'FileVersion', u'6.0.0.0'), + StringStruct(u'FileVersion', u'6.1.0.0'), StringStruct(u'InternalName', u'SciHubEVA'), StringStruct(u'LegalCopyright', u'\xa9 2018-2020 Leo Van.'), StringStruct(u'OriginalFilename', u'SciHubEVA.Exe'), StringStruct(u'ProductName', u'Sci-Hub EVA'), - StringStruct(u'ProductVersion', u'6.0.0.0')]) + StringStruct(u'ProductVersion', u'6.1.0.0')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ] diff --git a/building/build_macos_x64.sh b/building/build_macos_x64.sh index a665581..442b322 100755 --- a/building/build_macos_x64.sh +++ b/building/build_macos_x64.sh @@ -33,10 +33,10 @@ pyinstaller app.py \ cp building/macOS/Info.plist dist-macOS-x64/SciHubEVA.app/Contents +echo "Post process" python building/post_process.py dist-macOS-x64 echo "Package with create-dmg" - create-dmg \ --volname "Sci-Hub EVA" \ --volicon "building/macOS/SciHubEVA-dmg.icns" \ @@ -49,6 +49,6 @@ create-dmg \ --hide-extension "SciHubEVA.app" \ --app-drop-link 300 150 \ --eula "LICENSE" \ - --format "UDBZ" \ + --format "ULFO" \ "dist-macOS-x64/SciHubEVA-x64-latest.dmg" \ "dist-macOS-x64/SciHubEVA.app" diff --git a/building/build_windows_x64.ps1 b/building/build_windows_x64.ps1 index 2639a6e..c1bb0fe 100644 --- a/building/build_windows_x64.ps1 +++ b/building/build_windows_x64.ps1 @@ -41,11 +41,10 @@ pyinstaller app.py ` --clean ` --noupx +Write-Output "Post process" python building/post_process.py dist-Windows-x64 Write-Output "Package with Inno Setup" - Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "building\\Windows\\ChineseSimplified.isl" Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseTraditional.isl" -OutFile "building\\Windows\\ChineseTraditional.isl" - & "${Env:ProgramFiles(x86)}\\Inno Setup 6\\ISCC.exe" "building\\Windows\\SciHubEVA-x64.iss" diff --git a/building/convert_i18n_zh.py b/building/convert_i18n_zh.py index f278b43..446b829 100644 --- a/building/convert_i18n_zh.py +++ b/building/convert_i18n_zh.py @@ -8,18 +8,21 @@ if __name__ == '__main__': - tw_converter = opencc.OpenCC('s2twp.json') - hk_converter = opencc.OpenCC('s2hk.json') + tw_converter = opencc.OpenCC('s2twp') + hk_converter = opencc.OpenCC('s2hk') i18n_directory = Path(os.path.dirname(__file__)) / '..' / 'i18n' - with open(i18n_directory / 'SciHubEVA_zh_CN.ts', 'r', encoding='utf-8') as zh_i18n_f: + with open(i18n_directory / 'SciHubEVA_zh_CN.ts', 'r', + encoding='utf-8') as zh_i18n_f: zh_i18n_str = zh_i18n_f.read() hk_i18n_str = hk_converter.convert(zh_i18n_str) - with open(i18n_directory / 'SciHubEVA_zh_HK.ts', 'w', encoding='utf-8') as hk_i18n_f: + with open(i18n_directory / 'SciHubEVA_zh_HK.ts', 'w', + encoding='utf-8') as hk_i18n_f: hk_i18n_f.write(hk_i18n_str) tw_i18n_str = tw_converter.convert(zh_i18n_str) - with open(i18n_directory / 'SciHubEVA_zh_TW.ts', 'w', encoding='utf-8') as tw_i18n_f: + with open(i18n_directory / 'SciHubEVA_zh_TW.ts', 'w', + encoding='utf-8') as tw_i18n_f: tw_i18n_f.write(tw_i18n_str) diff --git a/building/macOS/Info.plist b/building/macOS/Info.plist index 9054114..8cc96c6 100644 --- a/building/macOS/Info.plist +++ b/building/macOS/Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable MacOS/SciHubEVA CFBundleShortVersionString - 6.0.0 + 6.1.0 LSBackgroundOnly 0 CFBundleName diff --git a/building/post_process.py b/building/post_process.py index aa12e7b..a4ccd48 100644 --- a/building/post_process.py +++ b/building/post_process.py @@ -37,15 +37,23 @@ 'Qt3DQuickScene2D', 'Qt3DRender', 'Qt5Compat', + 'QtBluetooth', 'QtBodymovin', 'QtCharts', 'QtChartsQml', 'QtDataVisualization', 'QtDataVisualizationQml', + 'QtDesigner', + 'QtDesignerComponents', + 'QtLabsAnimation', + 'QtLanguageServer', + 'QtLabsWavefrontMesh', + 'QtLocation', 'QtMultimedia', 'QtMultimediaQuick', - 'QtLabsAnimation' - 'QtLabsWavefrontMesh' + 'QtMultimediaWidgets', + 'QtNetworkAuth', + 'QtNfc', 'QtPositioning', 'QtPositioningQuick', 'QtQuick3D', @@ -65,11 +73,16 @@ 'QtScxmlQml', 'QtSensors', 'QtSensorsQuick', + 'QtSerialBus', + 'QtSerialPort', 'QtShaderTools', + 'QtSpatialAudio', 'QtSql', 'QtStateMachine', 'QtStateMachineQml', 'QtTest', + 'QtTextToSpeech', + 'QtUiTools', 'QtVirtualKeyboard', 'QtWebChannel', 'QtWebEngine', @@ -79,20 +92,57 @@ 'QtWebSockets', 'QtWebView', 'QtWebViewQuick', - 'QtXmlPatterns' + 'QtXmlPatterns', ] +USELESS_QT_DIRS_PREFIX = [ + 'examples', + 'glue', + 'include', + 'scripts', + 'support', + 'translations', + 'typesystems', + 'Qt/lib/cmake', + 'Qt/lib/metatypes', + 'Qt/lib/objects-RelWithDebInfo', + 'Qt/libexec', + 'Qt/metatypes', + 'Qt/plugins/assetimporters', + 'Qt/plugins/canbus', + 'Qt/plugins/designer', + 'Qt/plugins/generic', + 'Qt/plugins/geometryloaders', + 'Qt/plugins/multimedia', + 'Qt/plugins/networkinformation', + 'Qt/plugins/platforminputcontexts', + 'Qt/plugins/position', + 'Qt/plugins/qmltooling', + 'Qt/plugins/renderers', + 'Qt/plugins/renderplugins', + 'Qt/plugins/sceneparsers', + 'Qt/plugins/scxmldatamodel', + 'Qt/plugins/sensors', + 'Qt/plugins/sqldrivers', + 'Qt/plugins/texttospeech', + 'Qt/plugins/tls', + 'Qt/plugins/virtualkeyboard', + 'Qt/qml/QtLocation', + 'Qt/qml/QtPositioning', + 'Qt/qml/QtTextToSpeech', + 'Qt/translations', +] USELESS_PACKAGES = [ 'PyInstaller', 'tcl', 'tcl8', - 'tk' + 'tk', ] USELESS_LIBRARIES = [ 'tcl', - 'tk' + 'tk', ] @@ -118,6 +168,12 @@ def post_process_win(dist_folder): if os.path.isdir(qt_qml_dir): shutil.rmtree(qt_qml_dir, ignore_errors=True) + # remove useless Qt directories + for qt_file_prefix in USELESS_QT_DIRS_PREFIX: + qt_file_win_prefix = qt_file_prefix.replace('Qt/', '') + qt_dir = os.path.join(windows_app_path, 'PySide6', qt_file_win_prefix) + shutil.rmtree(qt_dir, ignore_errors=True) + # remove useless packages for package in USELESS_PACKAGES: package_dir = os.path.join(windows_app_path, package) @@ -128,13 +184,16 @@ def post_process_win(dist_folder): # remove useless libraries for library in USELESS_LIBRARIES: for lib in glob.glob( - os.path.join(windows_app_path, 'lib{}*.dll'.format(library)), recursive=False): + os.path.join( + windows_app_path, 'lib{}*.dll'.format( + library)), recursive=False): os.remove(lib) # remove useless directory for postfix in ['.egg-info', '.dist-info']: for directory in glob.glob( - os.path.join(windows_app_path, '*{}'.format(postfix)), recursive=False): + os.path.join(windows_app_path, '*{}'.format( + postfix)), recursive=False): if os.path.isdir(directory): shutil.rmtree(directory, ignore_errors=True) @@ -148,14 +207,23 @@ def post_process_macos(dist_folder): if os.path.exists(qt_lib_path): os.remove(qt_lib_path) - qt_qml_dir = os.path.join(macos_app_path, 'Contents', 'MacOS', 'PySide6', 'Qt', 'qml', qt_lib) + qt_qml_dir = os.path.join( + macos_app_path, 'Contents', 'MacOS', 'PySide6', 'Qt', 'qml', qt_lib) if os.path.isdir(qt_qml_dir): shutil.rmtree(qt_qml_dir, ignore_errors=True) + # remove useless Qt directories + for qt_file_prefix in USELESS_QT_DIRS_PREFIX: + qt_dir = os.path.join( + macos_app_path, 'Contents', 'MacOS', 'PySide6', qt_file_prefix) + shutil.rmtree(qt_dir, ignore_errors=True) + # remove useless packages for package in USELESS_PACKAGES: - package_dir = os.path.join(macos_app_path, 'Contents', 'Resources', package) - package_link = os.path.join(macos_app_path, 'Contents', 'MacOS', package) + package_dir = os.path.join( + macos_app_path, 'Contents', 'Resources', package) + package_link = os.path.join( + macos_app_path, 'Contents', 'MacOS', package) if os.path.isdir(package_dir): shutil.rmtree(package_dir, ignore_errors=True) @@ -166,18 +234,24 @@ def post_process_macos(dist_folder): # remove useless libraries for library in USELESS_LIBRARIES: for lib in glob.glob( - os.path.join(macos_app_path, 'Contents', 'MacOS', 'lib{}*.dylib'.format(library)), recursive=False): + os.path.join( + macos_app_path, 'Contents', 'MacOS', 'lib{}*.dylib'.format( + library)), recursive=False): os.remove(lib) # remove useless directory for postfix in ['.egg-info', '.dist-info']: for directory in glob.glob( - os.path.join(macos_app_path, 'Contents', 'Resources', '*{}'.format(postfix)), recursive=False): + os.path.join( + macos_app_path, 'Contents', 'Resources', '*{}'.format( + postfix)), recursive=False): if os.path.isdir(directory): shutil.rmtree(directory, ignore_errors=True) for directory in glob.glob( - os.path.join(macos_app_path, 'Contents', 'MacOS', '*{}'.format(postfix)), recursive=False): + os.path.join( + macos_app_path, 'Contents', 'MacOS', '*{}'.format( + postfix)), recursive=False): if os.path.islink(directory): os.remove(directory) diff --git a/design/SciHubEVA-background.psd b/design/SciHubEVA-background.psd index ee32eb1..e10b8d3 100644 Binary files a/design/SciHubEVA-background.psd and b/design/SciHubEVA-background.psd differ diff --git a/design/SciHubEVA-dmg-background.psd b/design/SciHubEVA-dmg-background.psd index ff8fc4a..ea02d2d 100644 Binary files a/design/SciHubEVA-dmg-background.psd and b/design/SciHubEVA-dmg-background.psd differ diff --git a/design/SciHubEVA-dmg-icon.psd b/design/SciHubEVA-dmg-icon.psd index a709afa..64d1847 100644 Binary files a/design/SciHubEVA-dmg-icon.psd and b/design/SciHubEVA-dmg-icon.psd differ diff --git a/design/SciHubEVA-icon.psd b/design/SciHubEVA-icon.psd index b83ea4f..85d9ada 100644 Binary files a/design/SciHubEVA-icon.psd and b/design/SciHubEVA-icon.psd differ diff --git a/design/SciHubEVA-inno-installer-icon.psd b/design/SciHubEVA-inno-installer-icon.psd index b7ef1e1..839b0ff 100644 Binary files a/design/SciHubEVA-inno-installer-icon.psd and b/design/SciHubEVA-inno-installer-icon.psd differ diff --git a/design/SciHubEVA-inno-installer-wizard-small.psd b/design/SciHubEVA-inno-installer-wizard-small.psd index f707110..7949460 100644 Binary files a/design/SciHubEVA-inno-installer-wizard-small.psd and b/design/SciHubEVA-inno-installer-wizard-small.psd differ diff --git a/design/SciHubEVA-inno-installer-wizard.psd b/design/SciHubEVA-inno-installer-wizard.psd index 4ec9a74..5c4bd06 100644 Binary files a/design/SciHubEVA-inno-installer-wizard.psd and b/design/SciHubEVA-inno-installer-wizard.psd differ diff --git a/docs/scihub-eva-application-macos-en-dark-theme.png b/docs/scihub-eva-application-macos-en-dark-theme.png index 4f4cbbc..b47566a 100644 Binary files a/docs/scihub-eva-application-macos-en-dark-theme.png and b/docs/scihub-eva-application-macos-en-dark-theme.png differ diff --git a/docs/scihub-eva-application-macos-en-light-theme.png b/docs/scihub-eva-application-macos-en-light-theme.png index 5f6a97d..74f0e23 100644 Binary files a/docs/scihub-eva-application-macos-en-light-theme.png and b/docs/scihub-eva-application-macos-en-light-theme.png differ diff --git a/docs/scihub-eva-application-macos-en.png b/docs/scihub-eva-application-macos-en.png deleted file mode 100644 index 9fc9a6b..0000000 Binary files a/docs/scihub-eva-application-macos-en.png and /dev/null differ diff --git a/docs/scihub-eva-application-windows-en-dark-theme.png b/docs/scihub-eva-application-windows-en-dark-theme.png index d741d7b..fcfefce 100755 Binary files a/docs/scihub-eva-application-windows-en-dark-theme.png and b/docs/scihub-eva-application-windows-en-dark-theme.png differ diff --git a/docs/scihub-eva-application-windows-en-light-theme.png b/docs/scihub-eva-application-windows-en-light-theme.png index c4d2bbd..29665d5 100755 Binary files a/docs/scihub-eva-application-windows-en-light-theme.png and b/docs/scihub-eva-application-windows-en-light-theme.png differ diff --git a/docs/scihub-eva-captcha-macos-en.png b/docs/scihub-eva-captcha-macos-en.png index 49d5887..92ac3a5 100644 Binary files a/docs/scihub-eva-captcha-macos-en.png and b/docs/scihub-eva-captcha-macos-en.png differ diff --git a/docs/scihub-eva-preferences-file-macos-en.png b/docs/scihub-eva-preferences-file-macos-en.png index 93e33eb..9ccbe9d 100644 Binary files a/docs/scihub-eva-preferences-file-macos-en.png and b/docs/scihub-eva-preferences-file-macos-en.png differ diff --git a/docs/scihub-eva-preferences-network-macos-en.png b/docs/scihub-eva-preferences-network-macos-en.png index d2c191a..0d54c74 100644 Binary files a/docs/scihub-eva-preferences-network-macos-en.png and b/docs/scihub-eva-preferences-network-macos-en.png differ diff --git a/docs/scihub-eva-preferences-system-macos-en.png b/docs/scihub-eva-preferences-system-macos-en.png index 9cfebc2..94b27e5 100644 Binary files a/docs/scihub-eva-preferences-system-macos-en.png and b/docs/scihub-eva-preferences-system-macos-en.png differ diff --git a/i18n/SciHubEVA_pt_PT.ts b/i18n/SciHubEVA_pt_PT.ts index 69b4394..cf22da7 100644 --- a/i18n/SciHubEVA_pt_PT.ts +++ b/i18n/SciHubEVA_pt_PT.ts @@ -4,12 +4,10 @@ About - is a cross-platform Sci-Hub GUI Application. é uma aplicação multi plataforma de interface gráfica do Sci-Hub. - Leo Van Leo Van @@ -17,22 +15,18 @@ AddSciHubURL - Add Sci-Hub URL Adicionar URL ao Sci-Hub - Sci-Hub URL: URL do Sci-Hub: - Confirm Confirmar - Cancel Cancelar @@ -40,22 +34,18 @@ Captcha - Captcha Captcha - Captcha: Captcha: - Confirm Confirmar - Cancel Cancelar @@ -63,152 +53,130 @@ Preferences - Preferences Preferências - OK OK - - - Yes Sim - - - No Não - - System Sistema - File Ficheiro - Network Rede - Language: Idioma: - Theme: Tema: - Light Claro - Dark Escuro - Changes will take effect after restart As alterações terão efeito após o reinício - Filename Prefix Format: Formato de prefixo de nome de arquivo: - Supported Keywords: Palavras-chave Suportadas: - - {author}: Author, {year}: Year, {title}: Title, {id}: DOI or PMID - {author}: Autor, {year}: Ano, {title}: Título, {id}: DOI ou PMID - - - Overwrite Existing File: Substituir o Ficheiro Existente: - + {author}: Author + {author}: Autor + + + {year}: Year + {year}: Ano + + + {title}: Title + {title}: Título + + + {id}: DOI or PMID + {id}: DOI ou PMID + + SciHub URL: SciHub URL: - Cannot remove the last Sci-Hub URL! Não foi possível remover o último URL do Sci-Hub! - Delete Sci-Hub URL: Eliminar URL do Sci-Hub: - Timeout: Tempo Esgotado: - Retry Times: Tentativas: - Enable Proxy Ativar Proxy - Proxy Type: Tipo do Proxy: - Proxy Host: - Anfitrião do Proxy: + Porta do Proxy: - Proxy Port: - Porta do Proxy: + Usuário do Proxy: - Proxy Username: - Usuário do Proxy: + Senha do Proxy: - Proxy Password: - Senha do Proxy: + Confirmar - Confirm Confirmar - Cancel Cancelar @@ -216,91 +184,91 @@ SciHubAPI - + Angel [CAPTCHA] down! Angel [CAPTCHA] caído! - + Fetching PDF ... Buscando PDF ... - + Angel [CAPTCHA] is coming! Angel [CAPTCHA] à caminho! - + Unknown PDF Content-Type! Tipo de Conteúdo do PDF, Inválido! - + Failed to get PDF! Falha ao obter PDF! - + Using Sci-Hub URL: Utilizando URL do Sci-Hub: - + Query type: Tipo de Consulta: - + Fetching PDF URL ... Buscando URL do PDF ... - - - + + + Error {} Erro {} - + Got PDF URL: URL do PDF Obtido: - - + + Failed to get PDF URL! Falha ao obter o URL do PDF! - - - - - - - + + + + + + + You may need check it manually. Você vai precisar lidar com isso manualmente. - + Unsupported filename keywords: Palavras-chave de nome de arquivo não suportadas: - + Saved PDF as: PDF Salvo como: - + Dealing with query: Processando Consulta: - + Wrong captcha, failed to kill Angel [CAPTCHA]! Captcha errado, falhou em matar Angel [CAPTCHA]! @@ -308,67 +276,54 @@ SciHubEVA - OK OK - Query: Consulta: - URL, PMID, DOI, Search String or Query List File URL, PMID, DOI, String de Pesquisa ou Arquivo de lista de Consulta - Rampage Tumultuar - Please choose save to directory first! Por favor primeiro selecione aonde salvar! - Please specify query! Por favor especifique o valor da consulta! - Load Carregar - Save to: Salvar em: - Open Abrir - Show Mostrar - Logs: Registros: - Open Log File Abrir Ficheiro de Registro - Open Log Directory Abrir Localização do Registro @@ -384,12 +339,12 @@ UISciHubEVA - + Query list file is not a text file! O arquivo da lista de consulta não é um arquivo de texto! - + Dealing with {}/{} query ... Lidando com {}/{} consulta ... diff --git a/i18n/SciHubEVA_zh_CN.ts b/i18n/SciHubEVA_zh_CN.ts index 2c85e07..cd753a0 100644 --- a/i18n/SciHubEVA_zh_CN.ts +++ b/i18n/SciHubEVA_zh_CN.ts @@ -4,12 +4,10 @@ About - is a cross-platform Sci-Hub GUI Application. 是一个跨平台的 Sci-Hub GUI 程序。 - Leo Van 范叶亮 @@ -17,22 +15,18 @@ AddSciHubURL - Add Sci-Hub URL 添加 Sci-Hub 网址 - Sci-Hub URL: Sci-Hub 网址: - Confirm 确定 - Cancel 取消 @@ -40,22 +34,18 @@ Captcha - Captcha 验证码 - Captcha: 验证码: - Confirm 确定 - Cancel 取消 @@ -63,152 +53,130 @@ Preferences - Preferences 配置 - OK 确定 - - - Yes - - - No - - System 系统 - File 文件 - Network 网络 - Language: 语言: - Theme: 主题: - Light 明亮 - Dark 黑暗 - Changes will take effect after restart 重启后生效 - Filename Prefix Format: 文件名前缀格式: - Supported Keywords: 支持的关键字: - - {author}: Author, {year}: Year, {title}: Title, {id}: DOI or PMID - {author}: 作者, {year}: 年, {title}: 标题, {id}: DOI 或 PMID + Overwrite Existing File: + 覆盖现存文件: - - Overwrite Existing File: - 覆盖现存文件: + {author}: Author + {author}:作者 + + + {year}: Year + {year}:年份 + + + {title}: Title + {title}:标题 + + + {id}: DOI or PMID + {id}:DOI 或 PMID - SciHub URL: SciHub URL: - Cannot remove the last Sci-Hub URL! 无法删除最后一个 Sci-Hub 网址! - Delete Sci-Hub URL: 删除 Sci-Hub 网址: - Timeout: 超时: - Retry Times: 重试次数: - Enable Proxy 开启代理 - Proxy Type: 代理类型: - Proxy Host: 代理地址: - Proxy Port: 代理端口: - Proxy Username: 代理用户名: - Proxy Password: 代理密码: - Confirm 确定 - Cancel 取消 @@ -216,91 +184,91 @@ SciHubAPI - + Angel [CAPTCHA] down! 使徒 [CAPTCHA] 已消灭! - + Fetching PDF ... 获取 PDF 中 ... - + Angel [CAPTCHA] is coming! 使徒 [CAPTCHA] 来袭! - + Unknown PDF Content-Type! 未知的 PDF 数据类型! - + Failed to get PDF! 获取 PDF URL 失败! - + Using Sci-Hub URL: 使用 Sci-Hub URL: - + Query type: 搜索类型: - + Fetching PDF URL ... 获取 PDF URL 中 ... - - - + + + Error {} 错误 {} - + Got PDF URL: 获取到 PDF URL: - - + + Failed to get PDF URL! 获取 PDF URL 失败! - - - - - - - + + + + + + + You may need check it manually. 你可能需要手动处理本次查询。 - + Unsupported filename keywords: 不支持的文件名关键字: - + Saved PDF as: 保存 PDF 为: - + Dealing with query: 正在处理搜索: - + Wrong captcha, failed to kill Angel [CAPTCHA]! 验证码错误,使徒 [CAPTCHA] 消灭失败! @@ -308,67 +276,54 @@ SciHubEVA - OK 确定 - Query: 搜索: - URL, PMID, DOI, Search String or Query List File URL, PMID, DOI, 标题或搜索列表文件 - Rampage 暴走 - Please choose save to directory first! 请先选择保存路径! - Please specify query! 请指定搜索内容! - Load 载入 - Save to: 保存至: - Open 打开 - Show 显示 - Logs: 日志: - Open Log File 打开日志文件 - Open Log Directory 打开日志目录 @@ -384,12 +339,12 @@ UISciHubEVA - + Query list file is not a text file! 搜索列表文件不是一个文本文件! - + Dealing with {}/{} query ... 正在处理 {}/{} 查询 ... diff --git a/requirements-dev.txt b/requirements-dev.txt index 72d0214..451d68d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,4 @@ -pyinstaller==5.4.1 +pyinstaller==5.11.0 flask==2.3.2 docopt==0.6.2 -opencc==1.1.4; platform_system!="Windows" -opencc==1.1.1; platform_system=="Windows" \ No newline at end of file +opencc-python-reimplemented==0.1.7 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f9b3a0a..f38c33e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -lxml==4.9.1 -pdfminer.six==20220524 -PySide6==6.3.2 +lxml==4.9.2 +pdfminer.six==20221105 +PySide6==6.5.0 requests==2.31.0 PySocks==1.7.1 -Pillow==9.3.0 -darkdetect==0.7.1 -pathvalidate==2.5.2 \ No newline at end of file +Pillow==9.5.0 +darkdetect==0.8.0 +pathvalidate==3.0.0 \ No newline at end of file diff --git a/scihub_eva/api/scihub_api.py b/scihub_eva/api/scihub_api.py index a00ef37..fc4bc02 100644 --- a/scihub_eva/api/scihub_api.py +++ b/scihub_eva/api/scihub_api.py @@ -4,7 +4,7 @@ import time from enum import Enum, unique -from urllib.parse import urlparse, urljoin +from urllib.parse import urlparse from lxml import etree from tempfile import NamedTemporaryFile from pathlib import Path @@ -45,7 +45,14 @@ class SciHubAPIError(Enum): class SciHubAPI(QObject, threading.Thread): - def __init__(self, query_input, query, logger, callback=None, rampage_type=None, **kwargs): + def __init__( + self, + query_input, + query, + logger, + callback=None, + rampage_type=None, + **kwargs): QObject.__init__(self) threading.Thread.__init__(self) @@ -61,9 +68,11 @@ def __init__(self, query_input, query, logger, callback=None, rampage_type=None, self._sess = get_session() def fetch_pdf_url(self, query): - scihub_url = Preferences.get_or_default(NETWORK_SCIHUB_URL_KEY, NETWORK_SCIHUB_URL_DEFAULT) + scihub_url = Preferences.get_or_default( + NETWORK_SCIHUB_URL_KEY, NETWORK_SCIHUB_URL_DEFAULT) self._logger.info( - self.tr('Using Sci-Hub URL: ') + '{scihub_url}'.format(scihub_url=scihub_url)) + self.tr('Using Sci-Hub URL: ') + + f'{scihub_url}') query_type = guess_query_type(query) self._logger.info(self.tr('Query type: ') + query_type.upper()) @@ -78,28 +87,46 @@ def fetch_pdf_url(self, query): pdf_url_response = self._sess.post( scihub_url, data={'request': query}, verify=False, timeout=Preferences.get_or_default( - NETWORK_TIMEOUT_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int) / 1000.0) + NETWORK_TIMEOUT_KEY, + NETWORK_RETRY_TIMES_DEFAULT, + type=int) / 1000.0) if pdf_url_response.status_code != 200: - self._logger.error(self.tr('Error {}').format(pdf_url_response.status_code)) - self._logger.info(self.tr('You may need check it manually.')) + self._logger.error(self.tr('Error {}').format( + pdf_url_response.status_code)) + self._logger.info( + self.tr('You may need check it manually.')) err = SciHubAPIError.UNKNOWN else: html = etree.HTML(pdf_url_response.content) - article = html.xpath('//div[@id="article"]/embed[1]') or \ - html.xpath('//div[@id="article"]/iframe[1]') if html is not None else None + article = \ + html.xpath('//div[@id="article"]/embed[1]') or \ + html.xpath('//div[@id="article"]/iframe[1]') if \ + html is not None else None if article and len(article) > 0: - pdf_url = urlparse(article[0].attrib['src'], scheme='http').geturl() - pdf_url = urljoin(pdf_url_response.url, pdf_url) - pdf_url_html = '{pdf_url}'.format(pdf_url=pdf_url) + pdf_url = urlparse(article[0].attrib['src']) + response_url = urlparse(pdf_url_response.url) - self._logger.info(self.tr('Got PDF URL: ') + pdf_url_html) + if pdf_url.scheme == '': + pdf_url = pdf_url._replace( + scheme=response_url.scheme) + + if pdf_url.netloc == '': + pdf_url = pdf_url._replace( + netloc=response_url.netloc) + + pdf_url = pdf_url.geturl() + pdf_url_html = f'{pdf_url}' + + self._logger.info( + self.tr('Got PDF URL: ') + pdf_url_html) else: err = SciHubAPIError.NO_VALID_PDF self._logger.error(self.tr('Failed to get PDF URL!')) - self._logger.info(self.tr('You may need check it manually.')) + self._logger.info( + self.tr('You may need check it manually.')) except Exception as e: err = SciHubAPIError.UNKNOWN @@ -118,13 +145,18 @@ def get_captcha_info(self, pdf_captcha_response): if len(imgs) > 0 and len(ids) > 0: captcha_id = ids[0].attrib['value'] - captcha_img_src = imgs[0].attrib['src'] + captcha_img_url = urlparse(imgs[0].attrib['src']) + response_url = urlparse(pdf_captcha_response.url) - if captcha_img_src.startswith('http'): - captcha_img_url = captcha_img_src - else: - scheme, netloc, *_ = urlparse(pdf_captcha_response.url, scheme='http') - captcha_img_url = scheme + '://' + netloc + captcha_img_src + if captcha_img_url.scheme == '': + captcha_img_url = captcha_img_url._replace( + scheme=response_url.scheme) + + if captcha_img_url.netloc == '': + captcha_img_url = captcha_img_url._replace( + netloc=response_url.netloc) + + captcha_img_url = captcha_img_url.geturl() return captcha_id, captcha_img_url @@ -155,11 +187,17 @@ def fetch_pdf_with_captcha(self, pdf_captcha_response): captcha_id, _ = self.get_captcha_info(pdf_captcha_response) pdf_response = self._sess.post( - pdf_captcha_response.url, data={'answer': self._captcha_answer, 'id': captcha_id}, verify=False, - timeout=Preferences.get_or_default(NETWORK_TIMEOUT_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int) / 1000.0) + pdf_captcha_response.url, + data={'answer': self._captcha_answer, 'id': captcha_id}, + verify=False, + timeout=Preferences.get_or_default( + NETWORK_TIMEOUT_KEY, + NETWORK_RETRY_TIMES_DEFAULT, + type=int) / 1000.0) if pdf_response.status_code != 200: - self._logger.error(self.tr('Error {}').format(pdf_response.status_code)) + self._logger.error(self.tr('Error {}').format( + pdf_response.status_code)) self._logger.info(self.tr('You may need check it manually.')) err = SciHubAPIError.UNKNOWN elif pdf_response.headers['Content-Type'] == 'application/pdf': @@ -180,10 +218,13 @@ def fetch_pdf(self, pdf_url): pdf_response = self._sess.get( pdf_url, verify=False, timeout=Preferences.get_or_default( - NETWORK_TIMEOUT_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int) / 1000.0) + NETWORK_TIMEOUT_KEY, + NETWORK_RETRY_TIMES_DEFAULT, + type=int) / 1000.0) if pdf_response.status_code != 200: - self._logger.error(self.tr('Error {}').format(pdf_response.status_code)) + self._logger.error(self.tr('Error {}').format( + pdf_response.status_code)) self._logger.info(self.tr('You may need check it manually.')) err = SciHubAPIError.UNKNOWN elif pdf_response.headers['Content-Type'] == 'application/pdf': @@ -206,10 +247,13 @@ def fetch_pdf(self, pdf_url): def save_pdf(self, pdf, filename): pdf_name_formatter = Preferences.get_or_default( - FILE_FILENAME_PREFIX_FORMAT_KEY, FILE_FILENAME_PREFIX_FORMAT_DEFAULT) + FILE_FILENAME_PREFIX_FORMAT_KEY, + FILE_FILENAME_PREFIX_FORMAT_DEFAULT) if not Preferences.get_or_default( - FILE_OVERWRITE_EXISTING_FILE_KEY, FILE_OVERWRITE_EXISTING_FILE_DEFAULT, type=bool): + FILE_OVERWRITE_EXISTING_FILE_KEY, + FILE_OVERWRITE_EXISTING_FILE_DEFAULT, + type=bool): pdf_name_formatter += '_' + str(round(time.time() * 1000000)) pdf_metadata = get_pdf_metadata(pdf) @@ -226,7 +270,8 @@ def save_pdf(self, pdf, filename): try: pdf_name = pdf_name_formatter.format(**pdf_metadata) except Exception as e: - self._logger.error(self.tr('Unsupported filename keywords: ') + pdf_name_formatter) + self._logger.error( + self.tr('Unsupported filename keywords: ') + pdf_name_formatter) return pdf_name = sanitize_filename(pdf_name, replacement_text='-') @@ -235,7 +280,7 @@ def save_pdf(self, pdf, filename): with open(pdf_path, 'wb') as fp: fp.write(pdf) - pdf_link = '{pdf_path}'.format(pdf_path=pdf_path) + pdf_link = f'{pdf_path}' self._logger.info(self.tr('Saved PDF as: ') + pdf_link) @@ -263,7 +308,8 @@ def rampage(self, query, rampage_type): # Fetch PDF with Captcha pdf, err = self.fetch_pdf_with_captcha(query) if err == SciHubAPIError.WRONG_CAPTCHA: - self._logger.error(self.tr('Wrong captcha, failed to kill Angel [CAPTCHA]!')) + self._logger.error( + self.tr('Wrong captcha, failed to kill Angel [CAPTCHA]!')) return None, err # Save PDF diff --git a/scihub_eva/api/scihub_fake_server.py b/scihub_eva/api/scihub_fake_server.py index 8b950e1..a661e57 100644 --- a/scihub_eva/api/scihub_fake_server.py +++ b/scihub_eva/api/scihub_fake_server.py @@ -18,16 +18,20 @@ def pdf_url_query(): return 'UNKNOWN', 400 -def pdf_url_response(host_url: str, request: str): - return ''' +def pdf_url_response(host_url: str, post_request: str): + return f'''
- + +
- '''.format(host_url=host_url, request=request) + ''' @app.route('/', methods=['GET']) @@ -35,7 +39,10 @@ def pdf_query(pdf: str): if pdf.find('captcha') != -1: return captcha_response(request.host_url, pdf) - return send_file(TemporaryFile(), mimetype='application/pdf', attachment_filename=pdf) + return send_file( + TemporaryFile(), + mimetype='application/pdf', + download_name=pdf) @app.route('/', methods=['POST']) @@ -45,23 +52,28 @@ def pdf_captcha_query(pdf: str): if post_answer.lower() != 'moment': return 'WRONG CAPTCHA!' else: - return send_file(TemporaryFile(), mimetype='application/pdf', attachment_filename=pdf) + return send_file( + TemporaryFile(), + mimetype='application/pdf', + download_name=pdf) def captcha_response(host_url: str, pdf: str): - return ''' + return f''' - + - '''.format(host_url=host_url, pdf=pdf.split('.')[0]) + ''' @app.route('/captcha-moment.png', methods=['GET']) def captcha_img(): - return send_file((IMAGES_DIR / 'captcha-moment.png').resolve().as_posix(), mimetype='image/png') + return send_file( + (IMAGES_DIR / 'captcha-moment.png').resolve().as_posix(), + mimetype='image/png') if __name__ == '__main__': diff --git a/scihub_eva/globals/preferences.py b/scihub_eva/globals/preferences.py index a517f92..b9d3f73 100644 --- a/scihub_eva/globals/preferences.py +++ b/scihub_eva/globals/preferences.py @@ -21,7 +21,11 @@ NETWORK_SCIHUB_URL_DEFAULT = 'https://sci-hub.se' NETWORK_SCIHUB_URLS_KEY = 'Network/SciHubURLs' -NETWORK_SCIHUB_URLS_DEFAULT = ['https://sci-hub.se', 'https://sci-hub.st', 'https://sci-hub.ru'] +NETWORK_SCIHUB_URLS_DEFAULT = [ + 'https://sci-hub.se', + 'https://sci-hub.st', + 'https://sci-hub.ru', +] NETWORK_TIMEOUT_KEY = 'Network/Timeout' NETWORK_TIMEOUT_DEFAULT = 3000 diff --git a/scihub_eva/globals/versions.py b/scihub_eva/globals/versions.py index 8c26931..1d9efb1 100644 --- a/scihub_eva/globals/versions.py +++ b/scihub_eva/globals/versions.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ORGANIZATION_NAME = 'LeoVan' -ORGANIZATION_DOMAIN = 'leovan.me' +ORGANIZATION_DOMAIN = 'leovan.tech' APPLICATION_NAME = 'SciHubEVA' -APPLICATION_VERSION = 'v6.0.0' +APPLICATION_VERSION = 'v6.1.0' diff --git a/scihub_eva/ui/add_scihub_url.py b/scihub_eva/ui/add_scihub_url.py index b7d53b4..725a351 100644 --- a/scihub_eva/ui/add_scihub_url.py +++ b/scihub_eva/ui/add_scihub_url.py @@ -31,7 +31,8 @@ def _connect(self): @Slot(str) def add_scihub_url(self, url): - scihub_available_urls = Preferences.get_or_default(NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT) + scihub_available_urls = Preferences.get_or_default( + NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT) if url not in scihub_available_urls: scihub_available_urls.append(url) diff --git a/scihub_eva/ui/preferences.py b/scihub_eva/ui/preferences.py index 3dce0f9..7fc15e6 100644 --- a/scihub_eva/ui/preferences.py +++ b/scihub_eva/ui/preferences.py @@ -50,67 +50,94 @@ def _connect(self): self._window.saveSystemLanguage.connect(self.save_system_language) self._window.saveSystemTheme.connect(self.save_system_theme) - self._window.saveFileFilenamePrefixFormat.connect(self.save_file_filename_prefix_format) - self._window.saveFileOverwriteExistingFile.connect(self.save_file_overwrite_existing_file) - self._window.saveNetworkSciHubURLs.connect(self.save_network_scihub_urls) + self._window.saveFileFilenamePrefixFormat.connect( + self.save_file_filename_prefix_format) + self._window.saveFileOverwriteExistingFile.connect( + self.save_file_overwrite_existing_file) + self._window.saveNetworkSciHubURLs.connect( + self.save_network_scihub_urls) self._window.saveNetworkSciHubURL.connect(self.save_network_scihub_url) self._window.saveNetworkTimeout.connect(self.save_network_timeout) - self._window.saveNetworkRetryTimes.connect(self.save_network_retry_times) - self._window.saveNetworkProxyEnabled.connect(self.save_network_proxy_enabled) + self._window.saveNetworkRetryTimes.connect( + self.save_network_retry_times) + self._window.saveNetworkProxyEnabled.connect( + self.save_network_proxy_enabled) self._window.saveNetworkProxyType.connect(self.save_network_proxy_type) self._window.saveNetworkProxyHost.connect(self.save_network_proxy_host) self._window.saveNetworkProxyPort.connect(self.save_network_proxy_port) - self._window.saveNetworkProxyUsername.connect(self.save_network_proxy_username) - self._window.saveNetworkProxyPassword.connect(self.save_network_proxy_password) + self._window.saveNetworkProxyUsername.connect( + self.save_network_proxy_username) + self._window.saveNetworkProxyPassword.connect( + self.save_network_proxy_password) self.show.connect(self._window.show) self.set_system_language.connect(self._window.setSystemLanguage) self.set_system_theme.connect(self._window.setSystemTheme) - self.set_file_filename_prefix_format.connect(self._window.setFileFilenamePrefixFormat) - self.set_file_overwrite_existing_file.connect(self._window.setFileOverwriteExistingFile) + self.set_file_filename_prefix_format.connect( + self._window.setFileFilenamePrefixFormat) + self.set_file_overwrite_existing_file.connect( + self._window.setFileOverwriteExistingFile) self.set_network_scihub_urls.connect(self._window.setNetworkSciHubURLs) self.set_network_scihub_url.connect(self._window.setNetworkSciHubURL) self.set_network_timeout.connect(self._window.setNetworkTimeout) self.set_network_retry_times.connect(self._window.setNetworkRetryTimes) - self.set_network_proxy_enabled.connect(self._window.setNetworkProxyEnabled) + self.set_network_proxy_enabled.connect( + self._window.setNetworkProxyEnabled) self.set_network_proxy_type.connect(self._window.setNetworkProxyType) self.set_network_proxy_host.connect(self._window.setNetworkProxyHost) self.set_network_proxy_port.connect(self._window.setNetworkProxyPort) - self.set_network_proxy_username.connect(self._window.setNetworkProxyUsername) - self.set_network_proxy_password.connect(self._window.setNetworkProxyPassword) + self.set_network_proxy_username.connect( + self._window.setNetworkProxyUsername) + self.set_network_proxy_password.connect( + self._window.setNetworkProxyPassword) def load_preferences(self): self.set_system_language.emit( Preferences.get_or_default(SYSTEM_LANGUAGE_KEY, SYSTEM_LANGUAGE)) self.set_system_theme.emit( - QT_QUICK_CONTROLS2_CONFIG.get_or_default('Material', 'Theme', SYSTEM_THEME_DEFAULT)) + QT_QUICK_CONTROLS2_CONFIG.get_or_default( + 'Material', 'Theme', SYSTEM_THEME_DEFAULT)) self.set_file_filename_prefix_format.emit( - Preferences.get_or_default(FILE_FILENAME_PREFIX_FORMAT_KEY, FILE_FILENAME_PREFIX_FORMAT_DEFAULT)) + Preferences.get_or_default( + FILE_FILENAME_PREFIX_FORMAT_KEY, + FILE_FILENAME_PREFIX_FORMAT_DEFAULT)) self.set_file_overwrite_existing_file.emit( Preferences.get_or_default( - FILE_OVERWRITE_EXISTING_FILE_KEY, FILE_OVERWRITE_EXISTING_FILE_DEFAULT, type=bool)) + FILE_OVERWRITE_EXISTING_FILE_KEY, + FILE_OVERWRITE_EXISTING_FILE_DEFAULT, type=bool)) self.set_network_scihub_urls.emit( - Preferences.get_or_default(NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT)) + Preferences.get_or_default( + NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT)) self.set_network_scihub_url.emit( - Preferences.get_or_default(NETWORK_SCIHUB_URL_KEY, NETWORK_SCIHUB_URL_DEFAULT)) + Preferences.get_or_default( + NETWORK_SCIHUB_URL_KEY, NETWORK_SCIHUB_URL_DEFAULT)) self.set_network_timeout.emit( - Preferences.get_or_default(NETWORK_TIMEOUT_KEY, NETWORK_TIMEOUT_DEFAULT, type=int)) + Preferences.get_or_default( + NETWORK_TIMEOUT_KEY, NETWORK_TIMEOUT_DEFAULT, type=int)) self.set_network_retry_times.emit( - Preferences.get_or_default(NETWORK_RETRY_TIMES_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int)) + Preferences.get_or_default( + NETWORK_RETRY_TIMES_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int)) self.set_network_proxy_enabled.emit( - Preferences.get_or_default(NETWORK_PROXY_ENABLE_KEY, NETWORK_PROXY_ENABLE_DEFAULT, type=bool)) + Preferences.get_or_default( + NETWORK_PROXY_ENABLE_KEY, + NETWORK_PROXY_ENABLE_DEFAULT, type=bool)) self.set_network_proxy_type.emit( - Preferences.get_or_default(NETWORK_PROXY_TYPE_KEY, NETWORK_PROXY_TYPE_DEFAULT)) + Preferences.get_or_default( + NETWORK_PROXY_TYPE_KEY, NETWORK_PROXY_TYPE_DEFAULT)) self.set_network_proxy_host.emit( - Preferences.get_or_default(NETWORK_PROXY_HOST_KEY, NETWORK_PROXY_HOST_DEFAULT)) + Preferences.get_or_default( + NETWORK_PROXY_HOST_KEY, NETWORK_PROXY_HOST_DEFAULT)) self.set_network_proxy_port.emit( - Preferences.get_or_default(NETWORK_PROXY_PORT_KEY, NETWORK_PROXY_PORT_DEFAULT)) + Preferences.get_or_default( + NETWORK_PROXY_PORT_KEY, NETWORK_PROXY_PORT_DEFAULT)) self.set_network_proxy_username.emit( - Preferences.get_or_default(NETWORK_PROXY_USERNAME_KEY, NETWORK_PROXY_USERNAME_DEFAULT)) + Preferences.get_or_default( + NETWORK_PROXY_USERNAME_KEY, NETWORK_PROXY_USERNAME_DEFAULT)) self.set_network_proxy_password.emit( - Preferences.get_or_default(NETWORK_PROXY_PASSWORD_KEY, NETWORK_PROXY_PASSWORD_DEFAULT)) + Preferences.get_or_default( + NETWORK_PROXY_PASSWORD_KEY, NETWORK_PROXY_PASSWORD_DEFAULT)) @property def window(self): @@ -123,13 +150,15 @@ def show_ui_add_scihub_url(self): @Slot(int) def remove_scihub_url(self, scihub_url_current_index): - scihub_available_urls = Preferences.get_or_default(NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT) + scihub_available_urls = Preferences.get_or_default( + NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT) del scihub_available_urls[scihub_url_current_index] Preferences.set(NETWORK_SCIHUB_URLS_KEY, scihub_available_urls) Preferences.set(NETWORK_SCIHUB_URL_KEY, scihub_available_urls[0]) - scihub_available_urls = Preferences.get_or_default(NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT) + scihub_available_urls = Preferences.get_or_default( + NETWORK_SCIHUB_URLS_KEY, NETWORK_SCIHUB_URLS_DEFAULT) self.set_network_scihub_urls.emit(scihub_available_urls) self.set_network_scihub_url.emit(scihub_available_urls[0]) diff --git a/scihub_eva/ui/scihub_eva.py b/scihub_eva/ui/scihub_eva.py index d99d301..7f1fa57 100644 --- a/scihub_eva/ui/scihub_eva.py +++ b/scihub_eva/ui/scihub_eva.py @@ -42,9 +42,12 @@ def __init__(self): super(UISciHubEVA, self).__init__() self._engine = QQmlApplicationEngine() - self._engine.rootContext().setContextProperty('APPLICATION_VERSION', APPLICATION_VERSION) - self._engine.rootContext().setContextProperty('PYTHON_VERSION', PYTHON_VERSION) - self._engine.rootContext().setContextProperty('QT_VERSION', QT_VERSION) + self._engine.rootContext().setContextProperty( + 'APPLICATION_VERSION', APPLICATION_VERSION) + self._engine.rootContext().setContextProperty( + 'PYTHON_VERSION', PYTHON_VERSION) + self._engine.rootContext().setContextProperty( + 'QT_VERSION', QT_VERSION) self._engine.load('qrc:/ui/SciHubEVA.qml') self._window = self._engine.rootObjects()[0] @@ -62,7 +65,8 @@ def __init__(self): self._query_list_length = 0 self._captcha_img_file_path = None - self._save_to_dir = Preferences.get_or_default(FILE_SAVE_TO_DIR_KEY, FILE_SAVE_TO_DIR_DEFAULT) + self._save_to_dir = Preferences.get_or_default( + FILE_SAVE_TO_DIR_KEY, FILE_SAVE_TO_DIR_DEFAULT) self.set_save_to_dir.emit(self._save_to_dir) @property @@ -74,7 +78,8 @@ def _connect(self): self._window.systemOpenSaveToDir.connect(self.system_open_save_to_dir) self._window.showUIPreference.connect(self.show_ui_preference) self._window.systemOpenLogFile.connect(self.system_open_log_file) - self._window.systemOpenLogDirectory.connect(self.system_open_log_directory) + self._window.systemOpenLogDirectory.connect( + self.system_open_log_directory) self._window.rampage.connect(self.rampage) self.set_save_to_dir.connect(self._window.setSaveToDir) @@ -124,7 +129,8 @@ def rampage(self, query_input): self.rampage_query_list() else: self._logger.error(LOGGER_SEP) - self._logger.error(self.tr('Query list file is not a text file!')) + self._logger.error( + self.tr('Query list file is not a text file!')) elif is_range_query(query_input): self._query_list = deque(gen_range_query_list(query_input)) self._query_list_length = len(self._query_list) @@ -136,7 +142,8 @@ def rampage_query_list(self): if self._query_list and len(self._query_list) > 0: self._logger.info(LOGGER_SEP) self._logger.info(self.tr('Dealing with {}/{} query ...').format( - self._query_list_length - len(self._query_list) + 1, self._query_list_length)) + self._query_list_length - len(self._query_list) + 1, + self._query_list_length)) self.rampage_query(self._query_list.popleft()) @@ -181,7 +188,8 @@ def show_captcha(self, pdf_captcha_response): logger=self._logger) _, captcha_img_url = scihub_api.get_captcha_info(pdf_captcha_response) invert_color = True if is_app_dark_theme() == 1 else False - captcha_img_file_path = scihub_api.download_captcha_img(captcha_img_url, invert_color=invert_color) + captcha_img_file_path = scihub_api.download_captcha_img( + captcha_img_url, invert_color=invert_color) self._captcha_img_file_path = captcha_img_file_path.resolve().as_posix() captcha_img_local_uri = captcha_img_file_path.as_uri() @@ -189,5 +197,6 @@ def show_captcha(self, pdf_captcha_response): center_window(self._ui_captcha.window, self._window) def remove_captcha_img(self): - if os.path.exists(self._captcha_img_file_path) and os.path.isfile(self._captcha_img_file_path): + if os.path.exists(self._captcha_img_file_path) and os.path.isfile( + self._captcha_img_file_path): os.remove(self._captcha_img_file_path) diff --git a/scihub_eva/utils/api_utils.py b/scihub_eva/utils/api_utils.py index 6c7ec0f..86adc30 100644 --- a/scihub_eva/utils/api_utils.py +++ b/scihub_eva/utils/api_utils.py @@ -10,7 +10,8 @@ RANGE_QUERY_PATTERN = re.compile(r'\{\d+\-\d+\}') -DOI_PATTERN = re.compile(r'\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'])\S)+)\b') +DOI_PATTERN = re.compile( + r'\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'])\S)+)\b') def is_range_query(query_input: str) -> bool: @@ -29,11 +30,14 @@ def gen_range_query_list(query_input: str) -> List[str]: if len(range_from) == len(range_to): digit = len(range_from) range_item_pattern = '{:0>' + str(digit) + 'd}' - range_items = [range_item_pattern.format(i) for i in range(int(range_from), int(range_to) + 1)] + range_items = [range_item_pattern.format(i) \ + for i in range(int(range_from), int(range_to) + 1)] else: - range_items = [str(i) for i in range(int(range_from), int(range_to) + 1)] + range_items = [ + str(i) for i in range(int(range_from), int(range_to) + 1)] - return [query_input.replace(range_pattern, range_item) for range_item in range_items] + return [query_input.replace(range_pattern, range_item) for range_item in + range_items] def guess_query_type(query): @@ -76,7 +80,8 @@ def get_pdf_metadata(pdf) -> dict: if title and title != '': metadata['title'] = title - year = pdf_metadata_moddate_to_year(make_pdf_metadata_str(pdf_metadata.get('ModDate', ''))) + year = pdf_metadata_moddate_to_year( + make_pdf_metadata_str(pdf_metadata.get('ModDate', ''))) if year and year != '': metadata['year'] = year except Exception as e: diff --git a/scihub_eva/utils/logging_utils.py b/scihub_eva/utils/logging_utils.py index 7ec2f56..d021ba2 100644 --- a/scihub_eva/utils/logging_utils.py +++ b/scihub_eva/utils/logging_utils.py @@ -12,15 +12,17 @@ DEFAULT_LOG_DIRECTORY = logs_dir() DEFAULT_LOG_FILE = DEFAULT_LOG_DIRECTORY / 'SciHubEVA.log' -DEFAULT_LOG_HANDLER = TimedRotatingFileHandler(DEFAULT_LOG_FILE.resolve().as_posix(), when='d', encoding='utf-8') +DEFAULT_LOG_HANDLER = TimedRotatingFileHandler( + DEFAULT_LOG_FILE.resolve().as_posix(), when='d', encoding='utf-8') DEFAULT_LOG_HANDLER.setLevel(logging.INFO) -DEFAULT_LOG_FORMATTER = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +DEFAULT_LOG_FORMATTER = logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s') DEFAULT_LOG_HANDLER.setFormatter(DEFAULT_LOG_FORMATTER) DEFAULT_LOGGER.addHandler(DEFAULT_LOG_HANDLER) -LOGGER_SEP = '-' * 100 +LOGGER_SEP = '–' * 30 def format_log_message(message): diff --git a/scihub_eva/utils/network_utils.py b/scihub_eva/utils/network_utils.py index c7c3825..9dd0858 100644 --- a/scihub_eva/utils/network_utils.py +++ b/scihub_eva/utils/network_utils.py @@ -14,24 +14,31 @@ def get_session(): 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' - 'Chrome/94.0.4606.81 ' + 'Chrome/113.0.0.0 ' 'Safari/537.36' } - retry_times = Preferences.get_or_default(NETWORK_RETRY_TIMES_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int) + retry_times = Preferences.get_or_default( + NETWORK_RETRY_TIMES_KEY, NETWORK_RETRY_TIMES_DEFAULT, type=int) adapter = HTTPAdapter(max_retries=retry_times) sess.mount('http://', adapter) sess.mount('https://', adapter) - proxy_enabled = Preferences.get_or_default(NETWORK_PROXY_ENABLE_KEY, NETWORK_PROXY_ENABLE_DEFAULT, type=bool) + proxy_enabled = Preferences.get_or_default( + NETWORK_PROXY_ENABLE_KEY, NETWORK_PROXY_ENABLE_DEFAULT, type=bool) if proxy_enabled: - proxy_type = Preferences.get_or_default(NETWORK_PROXY_TYPE_KEY, NETWORK_PROXY_TYPE_DEFAULT) - proxy_host = Preferences.get_or_default(NETWORK_PROXY_HOST_KEY, NETWORK_PROXY_HOST_DEFAULT) - proxy_port = Preferences.get_or_default(NETWORK_PROXY_PORT_KEY, NETWORK_PROXY_PORT_DEFAULT) - proxy_username = Preferences.get_or_default(NETWORK_PROXY_USERNAME_KEY, NETWORK_PROXY_USERNAME_DEFAULT) - proxy_password = Preferences.get_or_default(NETWORK_PROXY_PASSWORD_KEY, NETWORK_PROXY_PASSWORD_DEFAULT) + proxy_type = Preferences.get_or_default( + NETWORK_PROXY_TYPE_KEY, NETWORK_PROXY_TYPE_DEFAULT) + proxy_host = Preferences.get_or_default( + NETWORK_PROXY_HOST_KEY, NETWORK_PROXY_HOST_DEFAULT) + proxy_port = Preferences.get_or_default( + NETWORK_PROXY_PORT_KEY, NETWORK_PROXY_PORT_DEFAULT) + proxy_username = Preferences.get_or_default( + NETWORK_PROXY_USERNAME_KEY, NETWORK_PROXY_USERNAME_DEFAULT) + proxy_password = Preferences.get_or_default( + NETWORK_PROXY_PASSWORD_KEY, NETWORK_PROXY_PASSWORD_DEFAULT) proxy = proxy_type + '://' diff --git a/scihub_eva/utils/path_utils.py b/scihub_eva/utils/path_utils.py index 7ae4817..1a7a721 100644 --- a/scihub_eva/utils/path_utils.py +++ b/scihub_eva/utils/path_utils.py @@ -18,11 +18,14 @@ def logs_dir(): if is_macos(): - logs_dir_path = Path.home() / 'Library/Logs/' / ORGANIZATION_NAME / APPLICATION_NAME + logs_dir_path = Path.home() / 'Library/Logs/' / \ + ORGANIZATION_NAME / APPLICATION_NAME elif is_windows(): - logs_dir_path = Path.home() / 'AppData/Local/' / ORGANIZATION_NAME / APPLICATION_NAME / 'logs' + logs_dir_path = Path.home() / 'AppData/Local/' / \ + ORGANIZATION_NAME / APPLICATION_NAME / 'logs' elif is_linux(): - logs_dir_path = Path.home() / '/var/log/' / ORGANIZATION_NAME / APPLICATION_NAME + logs_dir_path = Path.home() / '/var/log/' / \ + ORGANIZATION_NAME / APPLICATION_NAME else: logs_dir_path = LOCAL_LOGS_DIR diff --git a/scihub_eva/utils/preferences_utils.py b/scihub_eva/utils/preferences_utils.py index d03bbe8..85a97bf 100644 --- a/scihub_eva/utils/preferences_utils.py +++ b/scihub_eva/utils/preferences_utils.py @@ -11,10 +11,18 @@ class Preferences(object): if is_macos(): QSettings.setDefaultFormat(QSettings.Format.NativeFormat) - SETTINGS = QSettings(QSettings.NativeFormat, QSettings.UserScope, ORGANIZATION_DOMAIN, APPLICATION_NAME) + SETTINGS = QSettings( + QSettings.NativeFormat, + QSettings.UserScope, + ORGANIZATION_DOMAIN, + APPLICATION_NAME) else: QSettings.setDefaultFormat(QSettings.Format.IniFormat) - SETTINGS = QSettings(QSettings.IniFormat, QSettings.UserScope, ORGANIZATION_DOMAIN, APPLICATION_NAME) + SETTINGS = QSettings( + QSettings.IniFormat, + QSettings.UserScope, + ORGANIZATION_DOMAIN, + APPLICATION_NAME) def __init__(self): super(Preferences, self).__init__() @@ -70,7 +78,8 @@ def save(self): self.write(f, space_around_delimiters=self._space_around_delimiters) -QT_QUICK_CONTROLS2_CONFIG = Config((PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix()) +QT_QUICK_CONTROLS2_CONFIG = Config( + (PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix()) def is_app_dark_theme(): diff --git a/scihub_eva/utils/sys_utils.py b/scihub_eva/utils/sys_utils.py index 52949b7..c298230 100644 --- a/scihub_eva/utils/sys_utils.py +++ b/scihub_eva/utils/sys_utils.py @@ -64,7 +64,8 @@ def is_system_dark_theme(): if is_windows(): k = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize') + winreg.HKEY_CURRENT_USER, + 'Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize') try: return winreg.QueryValueEx(k, 'AppsUseLightTheme')[0] == 0 except Exception: diff --git a/ui/About.qml b/ui/About.qml index 0621fac..ef589ad 100644 --- a/ui/About.qml +++ b/ui/About.qml @@ -5,24 +5,28 @@ import QtQuick.Window import QtQuick.Controls.Material Dialog { + id: dialog + x: (parent.width - width) / 2 y: (parent.height - height) / 2 modal: true + Material.roundedScale: Material.SmallScale + ColumnLayout { anchors.fill: parent RowLayout { Layout.fillWidth: true - spacing: 10 + spacing: 8 Image { Layout.alignment: Qt.AlignTop - sourceSize.height: 96 - sourceSize.width: 96 + sourceSize.height: 128 + sourceSize.width: 128 source: "qrc:/images/SciHubEVA-icon.png" } @@ -32,7 +36,7 @@ Dialog { "

" + "Sci-Hub EVA " + qsTr("is a cross-platform Sci-Hub GUI Application.") + "
" + "Powered By Python " + PYTHON_VERSION + " & Qt " + QT_VERSION + "

" + - "

Copyright (c) 2018-2022 " + qsTr("Leo Van") + + "

Copyright (c) 2018-2023 " + qsTr("Leo Van") + " The MIT License

" Layout.fillWidth: true diff --git a/ui/Preferences.qml b/ui/Preferences.qml index 7796768..4522b74 100644 --- a/ui/Preferences.qml +++ b/ui/Preferences.qml @@ -1,6 +1,10 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Controls.Material +import QtQuick.Controls.Material.impl +import QtQml.Models import QtQuick.Window import "." as UI @@ -187,88 +191,73 @@ ApplicationWindow { anchors.fill: parent anchors.margins: margin - focus: true + Layout.fillHeight: true + Layout.fillWidth: true RowLayout { - spacing: 0 - Layout.fillHeight: true Layout.fillWidth: true - ListModel { - id: listModelPreferencesTabButtons - - ListElement { - property string name: qsTr("System") - property string iconSource: "qrc:/images/icons/monitor.svg" - } + ColumnLayout { + id: columnLayoutPreferenceList - ListElement { - property string name: qsTr("File") - property string iconSource: "qrc:/images/icons/edit_file.svg" - } + Layout.fillHeight: true + Layout.fillWidth: true - ListElement { - property string name: qsTr("Network") - property string iconSource: "qrc:/images/icons/ethernet_on.svg" - } - } + Layout.minimumWidth: 120 + Layout.maximumWidth: 120 - Component { - id: itemDelegatePreferencesTabButtons + ListModel { + id: listModelPreferencesTools - ItemDelegate { - width: parent.width - highlighted: ListView.isCurrentItem + ListElement { + property string name: qsTr("System") + property string iconSource: "qrc:/images/icons/monitor.svg" + } - RowLayout { - Layout.fillHeight: true - Layout.fillWidth: true + ListElement { + property string name: qsTr("File") + property string iconSource: "qrc:/images/icons/edit_file.svg" + } - anchors.fill: parent - Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter + ListElement { + property string name: qsTr("Network") + property string iconSource: "qrc:/images/icons/ethernet_on.svg" + } + } - Image { - source: iconSource + Component { + id: itemDelegatePreferencesTools - sourceSize.height: labelDelegateToolsItemText.font.pointSize * 1.6 - sourceSize.width: labelDelegateToolsItemText.font.pointSize * 1.6 + UIElements.ItemDelegate { + width: parent.width - Layout.leftMargin: 6 + onClicked: { + listViewPreferencesTools.currentIndex = index + stackLayoutPreferencesPanel.currentIndex = index } + } + } - Label { - id: labelDelegateToolsItemText + ListView { + id: listViewPreferencesTools - text: name - font.weight: Font.Medium + Layout.fillWidth: true + Layout.fillHeight: true - Layout.rightMargin: 6 - Layout.fillWidth: true - } - } + model: listModelPreferencesTools + delegate: itemDelegatePreferencesTools - onClicked: { - listViewPreferencesTabButtons.currentIndex = index - stackLayoutPreferencesTabs.currentIndex = index + Component.onCompleted: { + listViewPreferencesTools.currentIndex = 0 + stackLayoutPreferencesPanel.currentIndex = 0 } } } - ListView { - id: listViewPreferencesTabButtons - - Layout.minimumWidth: 100 - Layout.minimumHeight: 100 - Layout.fillHeight: true - - model: listModelPreferencesTabButtons - delegate: itemDelegatePreferencesTabButtons - } - ToolSeparator { - id: toolSeparatorPreferences - + rightPadding: 3 + leftPadding: 3 bottomPadding: 0 topPadding: 0 @@ -276,333 +265,374 @@ ApplicationWindow { } StackLayout { - id: stackLayoutPreferencesTabs + id: stackLayoutPreferencesPanel currentIndex: 0 - Layout.minimumHeight: 380 - Layout.minimumWidth: 400 Layout.fillHeight: true Layout.fillWidth: true - Item { - id: itemTabPreferencesSystem + Layout.minimumWidth: 480 + Layout.minimumHeight: 360 - Layout.fillHeight: true - Layout.fillWidth: true + Item { + id: itemPreferencesSystem - GridLayout { - columnSpacing: 3 - rowSpacing: 0 + ScrollView { + id: scrollViewPreferencesSystem - anchors.right: parent.right - anchors.left: parent.left - anchors.top: parent.top + anchors.fill: parent - columns: 2 + ColumnLayout { + width: Math.max(implicitWidth, scrollViewPreferencesSystem.width) - Label { - text: qsTr("Language: ") - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - ComboBox { - id: comboBoxPreferencesSystemLanguage + Label { + text: qsTr("Language: ") + } - Layout.minimumWidth: 200 - Layout.fillWidth: true + ComboBox { + id: comboBoxPreferencesSystemLanguage - textRole: "text" - valueRole: "value" + Layout.fillWidth: true - model: [ - { text: "English", value: "en" }, - { text: "简体中文", value: "zh_CN" }, - { text: "繁體中文", value: "zh_HK" }, - { text: "正體中文", value: "zh_TW" }, - { text: "Português", value: "pt_PT" } - ] - } + textRole: "text" + valueRole: "value" - Label { - text: qsTr("Theme: ") - } + model: [ + { text: "English", value: "en" }, + { text: "简体中文", value: "zh_CN" }, + { text: "繁體中文", value: "zh_HK" }, + { text: "正體中文", value: "zh_TW" }, + { text: "Português", value: "pt_PT" } + ] + } + } - ComboBox { - id: comboBoxPreferencesSystemTheme + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - Layout.minimumWidth: 200 - Layout.fillWidth: true + Label { + text: qsTr("Theme: ") + } - textRole: "text" - valueRole: "value" + ComboBox { + id: comboBoxPreferencesSystemTheme - model: [ - { text: qsTr("System"), value: "System" }, - { text: qsTr("Light"), value: "Light" }, - { text: qsTr("Dark"), value: "Dark" } - ] - } + Layout.fillWidth: true - ToolSeparator { - rightPadding: 0 - leftPadding: 0 - Layout.fillWidth: true - Layout.columnSpan: 2 - orientation: Qt.Horizontal - } + textRole: "text" + valueRole: "value" - RowLayout { - Layout.fillWidth: true - Layout.columnSpan: 2 + model: [ + { text: qsTr("System"), value: "System" }, + { text: qsTr("Light"), value: "Light" }, + { text: qsTr("Dark"), value: "Dark" } + ] + } + } - Image { - Layout.alignment: Qt.AlignLeft | Qt.AlignTop + ToolSeparator { + Layout.fillWidth: true - source: "qrc:/images/icons/info.svg" - sourceSize.height: labelAttention.font.pointSize * 1.6 - sourceSize.width: labelAttention.font.pointSize * 1.6 + rightPadding: 0 + leftPadding: 0 + orientation: Qt.Horizontal } - Label { - id: labelAttention - text: qsTr("Changes will take effect after restart") + RowLayout { + Layout.fillWidth: true + + Image { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + + source: "qrc:/images/icons/info.svg" + sourceSize.height: labelAttention.font.pointSize * 1.6 + sourceSize.width: labelAttention.font.pointSize * 1.6 + } + + Label { + id: labelAttention + text: qsTr("Changes will take effect after restart") + } } } } } Item { - id: itemTabPreferencesFile + id: itemPreferencesFile - Layout.fillHeight: true - Layout.fillWidth: true + ScrollView { + id: scrollViewPreferencesFile - GridLayout { - columnSpacing: 3 - rowSpacing: 0 + anchors.fill: parent - anchors.right: parent.right - anchors.left: parent.left - anchors.top: parent.top + ColumnLayout { + width: Math.max(implicitWidth, scrollViewPreferencesFile.width) - columns: 2 + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - Label { - text: qsTr("Filename Prefix Format: ") - } + Label { + text: qsTr("Overwrite Existing File: ") + } - TextField { - id: textFieldPreferencesFileFilenamePrefixFormat + Switch { + id: switchPreferencesFileOverwrite + } + } - implicitWidth: 200 - Layout.fillWidth: true + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - placeholderText: "{id}_{year}_{author}_{title}" - selectByMouse: true - } + Label { + text: qsTr("Filename Prefix Format: ") + } - Label { - text: qsTr("Supported Keywords: ") + "
" + - qsTr("{author}: Author, {year}: Year, {title}: Title, {id}: DOI or PMID") - Layout.columnSpan: 2 - } + TextField { + id: textFieldPreferencesFileFilenamePrefixFormat - Label { - text: qsTr("Overwrite Existing File: ") - } + Layout.fillWidth: true + + selectByMouse: true + } + } + + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + Label { + text: qsTr("Supported Keywords: ") + + Layout.alignment: Qt.AlignTop + } + + ColumnLayout { + Label { + text: qsTr("{author}: Author") + } + + Label { + text: qsTr("{year}: Year") + } + + Label { + text: qsTr("{title}: Title") + } - Switch { - id: switchPreferencesFileOverwrite - text: checked ? qsTr("Yes") : qsTr("No") - display: AbstractButton.TextBesideIcon + Label { + text: qsTr("{id}: DOI or PMID") + } + } + } } } } Item { - id: itemTabPreferencesNetwork + id: itemPreferencesNetwork - Layout.fillHeight: true - Layout.fillWidth: true + ScrollView { + id: scrollViewPreferencesNetwork - GridLayout { - rowSpacing: 0 - columnSpacing: 3 - - anchors.right: parent.right - anchors.left: parent.left - anchors.top: parent.top + anchors.fill: parent - rows: 9 - columns: 2 + ColumnLayout { + width: Math.max(implicitWidth, scrollViewPreferencesNetwork.width) - Label { - text: qsTr("SciHub URL: ") - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - RowLayout { - Layout.fillWidth: true + Label { + text: qsTr("SciHub URL: ") + } - ComboBox { - id: comboBoxPreferencesNetworkSciHubURL + ComboBox { + id: comboBoxPreferencesNetworkSciHubURL - Layout.minimumWidth: 160 - Layout.fillWidth: true - } + Layout.fillWidth: true + } - RoundButton { - id: roundButtonPreferencesNetworkSciHubURLAdd - text: "+" + RoundButton { + id: roundButtonPreferencesNetworkSciHubURLAdd + text: "+" - onClicked: { - showUIAddSciHubURL() + onClicked: { + showUIAddSciHubURL() + } } - } - RoundButton { - id: roundButtonPreferencesNetworkSciHubURLRemove - text: "-" - - onClicked: { - if (comboBoxPreferencesNetworkSciHubURL.count <= 1) { - dialogPreferencesMessage.messageType = "error" - dialogPreferencesMessage.message = qsTr("Cannot remove the last Sci-Hub URL!") - dialogPreferencesMessage.open() - } else { - var message = qsTr("Delete Sci-Hub URL: ") + comboBoxPreferencesNetworkSciHubURL.currentText + " ?" - dialogPreferencesRemoveSciHubURLConfirmMessage.messageType = "question" - dialogPreferencesRemoveSciHubURLConfirmMessage.message = message - dialogPreferencesRemoveSciHubURLConfirmMessage.open() + RoundButton { + id: roundButtonPreferencesNetworkSciHubURLRemove + text: "-" + + onClicked: { + if (comboBoxPreferencesNetworkSciHubURL.count <= 1) { + dialogPreferencesMessage.messageType = "error" + dialogPreferencesMessage.message = qsTr("Cannot remove the last Sci-Hub URL!") + dialogPreferencesMessage.open() + } else { + var message = qsTr("Delete Sci-Hub URL: ") + comboBoxPreferencesNetworkSciHubURL.currentText + " ?" + dialogPreferencesRemoveSciHubURLConfirmMessage.messageType = "question" + dialogPreferencesRemoveSciHubURLConfirmMessage.message = message + dialogPreferencesRemoveSciHubURLConfirmMessage.open() + } } } } - } - Label { - text: qsTr("Timeout: ") - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - RowLayout { - width: 100 - height: 100 - TextField { - id: textFieldPreferencesNetworkTimeout + Label { + text: qsTr("Timeout: ") + } - implicitWidth: 60 - Layout.fillWidth: false + TextField { + id: textFieldPreferencesNetworkTimeout - horizontalAlignment: Text.AlignHCenter - selectByMouse: true - validator: RegularExpressionValidator { - regularExpression: /[0-9]+/ + Layout.fillWidth: true + + horizontalAlignment: Text.AlignHCenter + selectByMouse: true + validator: RegularExpressionValidator { + regularExpression: /[0-9]+/ + } } - } - Label { - text: "ms" - } - } + Label { + text: "ms" + } - Label { - text: qsTr("Retry Times: ") - } + ToolSeparator {} + + Label { + text: qsTr("Retry Times: ") + } - TextField { - id: textFieldPreferencesNetworkRetryTimes + TextField { + id: textFieldPreferencesNetworkRetryTimes - implicitWidth: 60 - Layout.fillWidth: false + Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - selectByMouse: true - validator: RegularExpressionValidator { - regularExpression: /[0-9]+/ + horizontalAlignment: Text.AlignHCenter + selectByMouse: true + validator: RegularExpressionValidator { + regularExpression: /[0-9]+/ + } + } } - } - Label { - text: qsTr("Enable Proxy") - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - Switch { - id: switchPreferencesNetworkEnableProxy - text: checked ? qsTr("Yes") : qsTr("No") - } + Label { + text: qsTr("Enable Proxy") + } - Label { - text: qsTr("Proxy Type: ") - } + Switch { + id: switchPreferencesNetworkEnableProxy + } - RowLayout { - width: 100 - height: 100 + ToolSeparator {} - RadioButton { - id: radioButtonPreferencesNetworkProxyTypeHTTP - text: "HTTP" - } + Label { + text: qsTr("Proxy Type: ") + } - RadioButton { - id: radioButtonPreferencesNetworkProxyTypeSocks5 - text: "SOCKS5" + RadioButton { + id: radioButtonPreferencesNetworkProxyTypeHTTP + text: "HTTP" + } + + RadioButton { + id: radioButtonPreferencesNetworkProxyTypeSocks5 + text: "SOCKS5" + } } - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - Label { - text: qsTr("Proxy Host: ") - } + Label { + text: qsTr("Proxy Host: ") + } - TextField { - id: textFieldPreferencesNetworkProxyHost + TextField { + id: textFieldPreferencesNetworkProxyHost - implicitWidth: 200 - Layout.fillWidth: true + Layout.fillWidth: true - selectByMouse: true - } + selectByMouse: true + } + } - Label { - text: qsTr("Proxy Port: ") - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + Label { + text: qsTr("Proxy Port: ") + } - TextField { - id: textFieldPreferencesNetworkProxyPort + TextField { + id: textFieldPreferencesNetworkProxyPort - implicitWidth: 200 - Layout.fillWidth: true + Layout.fillWidth: true - selectByMouse: true - validator: RegularExpressionValidator { - regularExpression: /[0-9]+/ + selectByMouse: true + validator: RegularExpressionValidator { + regularExpression: /[0-9]+/ + } + } } - } - Label { - text: qsTr("Proxy Username: ") - } + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true - TextField { - id: textFieldPreferencesNetworkProxyUsername + Label { + text: qsTr("Proxy Username: ") + } - implicitWidth: 200 - Layout.fillWidth: true + TextField { + id: textFieldPreferencesNetworkProxyUsername - selectByMouse: true - } + Layout.fillWidth: true - Label { - text: qsTr("Proxy Password: ") - } + selectByMouse: true + } + } + + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + Label { + text: qsTr("Proxy Password: ") + } - TextField { - id: textFieldPreferencesNetworkProxyPassword + TextField { + id: textFieldPreferencesNetworkProxyPassword - implicitWidth: 200 - Layout.fillWidth: true + Layout.fillWidth: true - echoMode: TextInput.Password + echoMode: TextInput.Password + } + } } } } @@ -640,5 +670,3 @@ ApplicationWindow { } } } - - diff --git a/ui/SciHubEVA.qml b/ui/SciHubEVA.qml index 73b40b7..e9e0074 100644 --- a/ui/SciHubEVA.qml +++ b/ui/SciHubEVA.qml @@ -16,7 +16,7 @@ ApplicationWindow { visible: true - property int margin: 10 + property int margin: 8 property int theme: Material.theme width: columnLayoutApplication.implicitWidth + 2 * margin @@ -165,6 +165,7 @@ ApplicationWindow { text: qsTr("Rampage") font.bold: false + Layout.minimumWidth: implicitWidth Layout.fillWidth: true onClicked: { @@ -187,6 +188,7 @@ ApplicationWindow { text: qsTr("Load") font.bold: false + Layout.minimumWidth: implicitWidth Layout.fillWidth: true onClicked: fileDialogQueryList.open() @@ -221,6 +223,7 @@ ApplicationWindow { text: qsTr("Open") font.bold: false + Layout.minimumWidth: implicitWidth Layout.fillWidth: true onClicked: folderDialogSaveTo.open() @@ -231,6 +234,7 @@ ApplicationWindow { text: qsTr("Show") font.bold: false + Layout.minimumWidth: implicitWidth Layout.fillWidth: true onClicked: systemOpenSaveToDir(textFieldSaveToDir.text.trim()) @@ -256,26 +260,28 @@ ApplicationWindow { flickableDirection: Flickable.VerticalFlick - Layout.minimumWidth: 600 Layout.minimumHeight: 200 + Layout.fillWidth: true - function scrollToBottom() { - if (contentHeight > height) { - contentY = contentHeight - height - } + ScrollBar.vertical: UIElements.ScrollBar { + id: scrollBarLogs } TextArea.flickable: TextArea { id: textAreaLogs - font.pointSize: labelLogs.font.pointSize textFormat: Text.RichText wrapMode: Text.WordWrap readOnly: true selectByMouse: true horizontalAlignment: Text.AlignLeft - onTextChanged: flickableLogs.scrollToBottom() + Layout.fillWidth: true + + onTextChanged: { + scrollBarLogs.position = 1.0 - scrollBarLogs.size + } + onLinkActivated: (link) => { Qt.openUrlExternally(link) } diff --git a/ui/elements/IconButton.qml b/ui/elements/IconButton.qml index bf182d5..b17d6c2 100644 --- a/ui/elements/IconButton.qml +++ b/ui/elements/IconButton.qml @@ -6,14 +6,29 @@ Button { id: button property string iconSource - property real iconSize: labelText.font.pointSize * 1.6 + readonly property real iconSize: 20 + readonly property bool hasIcon: iconSource.toString().length > 0 + readonly property bool hasText: button.text.toString().length > 0 + readonly property bool hasOnlyIcon: hasIcon && !hasText - implicitWidth: leftPadding + rightPadding + rowContent.implicitWidth + topInset: 6 + bottomInset: 6 + verticalPadding: 14 + leftPadding: hasOnlyIcon ? 8 : hasIcon ? 16 : 24 + rightPadding: hasOnlyIcon ? 8 : 24 + + implicitWidth: hasIcon ? + Math.min(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) : + Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) contentItem: Row { id: rowContent - spacing: 6 + spacing: 8 anchors.horizontalCenter: parent.horizontalCenter AnimatedImage { @@ -21,13 +36,13 @@ Button { playing: true source: iconSource height: iconSize - width: iconSize + width: hasIcon ? iconSize : 0 anchors.verticalCenter: parent.verticalCenter } Label { id: labelText - text: parent.parent.text + text: button.text anchors.verticalCenter: parent.verticalCenter } } diff --git a/ui/elements/ItemDelegate.qml b/ui/elements/ItemDelegate.qml new file mode 100644 index 0000000..354fa89 --- /dev/null +++ b/ui/elements/ItemDelegate.qml @@ -0,0 +1,60 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.impl +import QtQuick.Controls.Material +import QtQuick.Controls.Material.impl +import QtQml.Models + +ItemDelegate { + id: control + + readonly property real iconSize: 20 + + highlighted: ListView.isCurrentItem + + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + anchors.fill: parent + Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter + + Image { + source: iconSource + + sourceSize.height: iconSize + sourceSize.width: iconSize + + Layout.leftMargin: 8 + } + + Label { + id: labelDelegateToolsItemText + + text: name + font.weight: Font.Medium + + Layout.rightMargin: 8 + Layout.fillWidth: true + } + } + + background: Rectangle { + implicitHeight: control.Material.delegateHeight + color: control.highlighted ? control.Material.listHighlightColor : "transparent" + radius: Material.ExtraSmallScale + + Ripple { + width: parent.width + height: parent.height + + clip: visible + pressed: control.pressed + anchor: control + active: enabled && (control.down || control.visualFocus || control.hovered) + color: control.Material.rippleColor + clipRadius: Material.ExtraSmallScale + } + } +} diff --git a/ui/elements/Message.qml b/ui/elements/Message.qml index 05abc0f..98fdfed 100644 --- a/ui/elements/Message.qml +++ b/ui/elements/Message.qml @@ -4,18 +4,25 @@ import QtQuick.Controls import QtQuick.Window Dialog { + id: dialog + x: (parent.width - width) / 2 y: (parent.height - height) / 2 modal: true closePolicy: Popup.NoAutoClose + Material.roundedScale: Material.SmallScale property string message property string messageType ColumnLayout { anchors.fill: parent + focus: true + + Keys.onReturnPressed: dialog.accept() + Keys.onEscapePressed: dialog.reject() RowLayout { Layout.fillWidth: true diff --git a/ui/elements/ScrollBar.qml b/ui/elements/ScrollBar.qml new file mode 100644 index 0000000..270501a --- /dev/null +++ b/ui/elements/ScrollBar.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Templates as T +import QtQuick.Controls.Material + +T.ScrollBar { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + padding: control.interactive ? 1 : 2 + visible: control.policy !== T.ScrollBar.AlwaysOff + minimumSize: orientation === Qt.Horizontal ? height / width : width / height + + contentItem: Rectangle { + implicitWidth: control.interactive ? 9 : 4 + implicitHeight: control.interactive ? 9 : 4 + + color: control.pressed ? control.Material.scrollBarPressedColor : + control.interactive && control.hovered ? control.Material.scrollBarHoveredColor : control.Material.scrollBarColor + opacity: 0.0 + radius: Material.ExtraSmallScale + } + + background: Rectangle { + implicitWidth: control.interactive ? 12 : 4 + implicitHeight: control.interactive ? 12 : 4 + color: "#0e000000" + opacity: 0.0 + radius: Material.ExtraSmallScale + visible: control.interactive + } + + states: State { + name: "active" + when: control.policy === T.ScrollBar.AlwaysOn || (control.active && control.size < 1.0) + } + + transitions: [ + Transition { + to: "active" + NumberAnimation { targets: [control.contentItem, control.background]; property: "opacity"; to: 1.0 } + }, + Transition { + from: "active" + SequentialAnimation { + PropertyAction{ targets: [control.contentItem, control.background]; property: "opacity"; value: 1.0 } + PauseAnimation { duration: 2450 } + NumberAnimation { targets: [control.contentItem, control.background]; property: "opacity"; to: 0.0 } + } + } + ] +}