├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── DeepQt.desktop ├── Glossary Templates ├── Glossary_template.ods ├── Glossary_template.xlsx ├── Glossary_template_JP.ods └── Glossary_template_JP.xlsx ├── LICENSE ├── Makefile ├── README.md ├── build-pyinstaller.bat ├── deepqt ├── CustomQ │ ├── CComboBox.py │ ├── CDropFrame.py │ ├── CListWidget.py │ ├── CTableWidget.py │ ├── CTextEdit.py │ ├── CTooltipLabel.py │ └── __init__.py ├── __init__.py ├── backend_settings.py ├── backends │ ├── __init__.py │ ├── backend_interface.py │ ├── deepl_backend.py │ ├── lookups.py │ └── mock_backend.py ├── config.py ├── constants.py ├── data │ ├── LiberationSans-Regular.ttf │ ├── NotoMono-Regular.ttf │ ├── __init__.py │ ├── color_themes │ │ ├── __init__.py │ │ ├── breeze │ │ └── breeze-dark │ ├── custom_icons │ │ ├── __init__.py │ │ ├── dark │ │ │ ├── cost-warning.svg │ │ │ └── unreliable-service.svg │ │ ├── deepl.png │ │ ├── generic-backend.svg │ │ ├── heart.svg │ │ ├── light │ │ │ ├── cost-warning.svg │ │ │ └── unreliable-service.svg │ │ ├── logo.ico │ │ └── logo.svg │ └── theme_icons │ │ ├── __init__.py │ │ ├── breeze-dark │ │ ├── actions │ │ │ └── 16 │ │ │ │ ├── application-menu.svg │ │ │ │ ├── arrow-down.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── configure.svg │ │ │ │ ├── dialog-cancel.svg │ │ │ │ ├── dialog-ok-apply.svg │ │ │ │ ├── dialog-ok.svg │ │ │ │ ├── document-multiple.svg │ │ │ │ ├── document-open-folder.svg │ │ │ │ ├── document-open.svg │ │ │ │ ├── document-preview.svg │ │ │ │ ├── document-save.svg │ │ │ │ ├── download.svg │ │ │ │ ├── edit-clear.svg │ │ │ │ ├── edit-copy.svg │ │ │ │ ├── edit-delete-remove.svg │ │ │ │ ├── edit-delete.svg │ │ │ │ ├── games-config-theme.svg │ │ │ │ ├── help-contents.svg │ │ │ │ ├── help-hint.svg │ │ │ │ ├── internet-services.svg │ │ │ │ ├── list-add.svg │ │ │ │ ├── media-playback-start.svg │ │ │ │ ├── process-stop.svg │ │ │ │ ├── tools-report-bug.svg │ │ │ │ ├── view-list-text.svg │ │ │ │ ├── view-refresh.svg │ │ │ │ └── window-close.svg │ │ ├── index.theme │ │ ├── mimetypes │ │ │ ├── 16 │ │ │ │ └── text-x-changelog.svg │ │ │ └── 32 │ │ │ │ ├── application-epub+zip.svg │ │ │ │ └── text-x-generic.svg │ │ └── status │ │ │ ├── 16 │ │ │ ├── data-error.svg │ │ │ ├── data-warning.svg │ │ │ ├── dialog-warning.svg │ │ │ ├── image-missing.svg │ │ │ ├── state-error.svg │ │ │ ├── state-offline.svg │ │ │ └── state-ok.svg │ │ │ └── 22 │ │ │ └── dialog-error.svg │ │ └── breeze │ │ ├── actions │ │ └── 16 │ │ │ ├── application-menu.svg │ │ │ ├── arrow-down.svg │ │ │ ├── arrow-up.svg │ │ │ ├── configure.svg │ │ │ ├── dialog-cancel.svg │ │ │ ├── dialog-ok-apply.svg │ │ │ ├── dialog-ok.svg │ │ │ ├── document-multiple.svg │ │ │ ├── document-open-folder.svg │ │ │ ├── document-open.svg │ │ │ ├── document-preview.svg │ │ │ ├── document-save.svg │ │ │ ├── download.svg │ │ │ ├── edit-clear.svg │ │ │ ├── edit-copy.svg │ │ │ ├── edit-delete-remove.svg │ │ │ ├── edit-delete.svg │ │ │ ├── games-config-theme.svg │ │ │ ├── help-contents.svg │ │ │ ├── help-hint.svg │ │ │ ├── internet-services.svg │ │ │ ├── list-add.svg │ │ │ ├── media-playback-start.svg │ │ │ ├── process-stop.svg │ │ │ ├── tools-report-bug.svg │ │ │ ├── view-list-text.svg │ │ │ ├── view-refresh.svg │ │ │ └── window-close.svg │ │ ├── index.theme │ │ ├── mimetypes │ │ ├── 16 │ │ │ └── text-x-changelog.svg │ │ └── 32 │ │ │ ├── application-epub+zip.svg │ │ │ └── text-x-generic.svg │ │ └── status │ │ ├── 16 │ │ ├── data-error.svg │ │ ├── data-warning.svg │ │ ├── dialog-warning.svg │ │ ├── image-missing.svg │ │ ├── state-error.svg │ │ ├── state-offline.svg │ │ └── state-ok.svg │ │ └── 22 │ │ └── dialog-error.svg ├── driver_api_key_input.py ├── driver_api_status_usage.py ├── driver_backend_configuration.py ├── driver_epub_preview.py ├── driver_mainwindow.py ├── driver_text_preview.py ├── error_dialog_driver.py ├── file_table.py ├── glossary.py ├── gui_utils.py ├── issue_reporter_driver.py ├── key_button.py ├── log_parser.py ├── log_viewer.py ├── main.py ├── memory_watcher.py ├── quote_protection.py ├── state_saver.py ├── structures.py ├── term_extractor.py ├── translation_interface.py ├── trie.py ├── ui_generated_files │ ├── __init__.py │ ├── ui_ErrorDialog.py │ ├── ui_IssueReporter.py │ ├── ui_api_key_deepl.py │ ├── ui_api_key_input.py │ ├── ui_api_overview_remote.py │ ├── ui_api_status_usage.py │ ├── ui_backend_configuration.py │ ├── ui_epub_preview.py │ ├── ui_mainwindow.py │ └── ui_text_preview.py ├── utils.py ├── worker_thread.py └── xml_parser.py ├── docs ├── api_help.md └── glossary_help.md ├── flatpak └── io.github.voxelcubes.deepqt.appdata.xml ├── icons ├── build_icon_cache.py ├── copy_from_dark_to_light.py └── theme_list.yaml ├── media ├── Web card.png ├── Web card.xcf ├── account_config.png ├── api_key.png ├── deepqt.png ├── example_screenshot.png └── example_screenshot_windows.png ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── helpers.py ├── mock_files │ ├── __init__.py │ ├── blank_file │ ├── config │ │ ├── __init__.py │ │ ├── coercible_types.json │ │ ├── empty.json │ │ ├── good.json │ │ ├── invalid.json │ │ ├── missing_and_extra_keys.json │ │ ├── missing_backend.json │ │ ├── type_mismatch.json │ │ └── unknown_backend.json │ ├── logs │ │ ├── __init__.py │ │ └── good.log │ ├── mime_types │ │ ├── __init__.py │ │ ├── archive.zip │ │ ├── book.epub │ │ ├── calc.ods │ │ ├── document.pdf │ │ ├── excel.xlsx │ │ ├── impress.odp │ │ ├── powerpoint.pptx │ │ ├── text.txt │ │ ├── translation.xliff │ │ ├── website.html │ │ ├── word.docx │ │ └── writer.odt │ └── various_encodings │ │ ├── __init__.py │ │ ├── generate.py │ │ ├── message_ascii.txt │ │ ├── message_big5.txt │ │ ├── message_euckr.txt │ │ ├── message_gb2312.txt │ │ ├── message_iso8859-1.txt │ │ ├── message_iso8859-5.txt │ │ ├── message_shiftjis.txt │ │ ├── message_utf16.txt │ │ ├── message_utf8.txt │ │ └── message_windows1251.txt ├── test_backend_interface.py ├── test_encoding_recognition.py ├── test_log_parser.py ├── test_mime_recognition.py ├── test_reading_config.py ├── test_theming.py └── test_writing_config.py └── ui_files ├── ErrorDialog.ui ├── IssueReporter.ui ├── api_key_deepl.ui ├── api_key_input.ui ├── api_overview_remote.ui ├── api_status_usage.ui ├── backend_configuration.ui ├── epub_preview.ui ├── mainwindow.ui └── text_preview.ui /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: voxelcode 2 | -------------------------------------------------------------------------------- /DeepQt.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=DeepQt 4 | Comment= Harness the power of the DeepL API. 5 | GenericName=Computer Aided Translation 6 | Icon=deepqt 7 | Exec=deepqt 8 | Terminal=false 9 | Categories=Utility 10 | -------------------------------------------------------------------------------- /Glossary Templates/Glossary_template.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/Glossary Templates/Glossary_template.ods -------------------------------------------------------------------------------- /Glossary Templates/Glossary_template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/Glossary Templates/Glossary_template.xlsx -------------------------------------------------------------------------------- /Glossary Templates/Glossary_template_JP.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/Glossary Templates/Glossary_template_JP.ods -------------------------------------------------------------------------------- /Glossary Templates/Glossary_template_JP.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/Glossary Templates/Glossary_template_JP.xlsx -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # define variables 2 | PYTHON = venv/bin/python 3 | CurrentDir := $(shell pwd) 4 | BUILD_DIR = dist 5 | BUILD_CACHE = deepqt.egg-info build 6 | DIR_ICONS := icons 7 | UI_DIR := ui_files 8 | UI_OUTPUT_DIR := deepqt/ui_generated_files 9 | UIC_COMPILER := venv/bin/pyside6-uic 10 | BLACK_LINE_LENGTH := 100 11 | BLACK_TARGET_DIR := deepqt/ 12 | BLACK_EXCLUDE_PATTERN := "^$(UI_OUTPUT_DIR)/.*" 13 | 14 | # default target 15 | fresh-install: clean build install 16 | 17 | refresh-assets: build-icon-cache compile-ui 18 | 19 | # build target 20 | build: 21 | $(PYTHON) -m build --outdir $(BUILD_DIR) 22 | 23 | # install target 24 | install: 25 | $(PYTHON) -m pip install $(BUILD_DIR)/*.whl 26 | 27 | # clean target 28 | clean: 29 | rm -rf $(BUILD_DIR) 30 | rm -rf $(BUILD_CACHE) 31 | rm -rf AUR/deepqt/pkg 32 | rm -rf AUR/deepqt/src 33 | rm -rf AUR/deepqt/*.tar.gz 34 | rm -rf AUR/deepqt/*.tar.zst 35 | 36 | release: confirm 37 | $(PYTHON) -m twine upload $(BUILD_DIR)/* 38 | 39 | # compile .ui files 40 | compile-ui: 41 | for file in $(UI_DIR)/*.ui; do \ 42 | basename=`basename $$file .ui`; \ 43 | $(UIC_COMPILER) $$file -o $(UI_OUTPUT_DIR)/ui_$$basename.py; \ 44 | done 45 | 46 | build-icon-cache: 47 | $(PYTHON) $(DIR_ICONS)/build_icon_cache.py 48 | $(PYTHON) $(DIR_ICONS)/copy_from_dark_to_light.py 49 | # format the code 50 | black-format: 51 | find $(BLACK_TARGET_DIR) -type f -name '*.py' | grep -Ev $(BLACK_EXCLUDE_PATTERN) | xargs black --line-length $(BLACK_LINE_LENGTH) 52 | 53 | confirm: 54 | @read -p "Are you sure you want to proceed? (yes/no): " CONFIRM; \ 55 | if [ "$$CONFIRM" = "yes" ]; then \ 56 | echo "Proceeding..."; \ 57 | else \ 58 | echo "Aborted by user."; \ 59 | exit 1; \ 60 | fi 61 | 62 | .PHONY: confirm clean build install fresh-install release black-format compile-ui build-icon-cache refresh-assets -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeepQt 2 | 3 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 4 | [![PyPI version](https://img.shields.io/pypi/v/deepqt)](https://pypi.org/project/deepqt/) 5 | [![AUR version](https://img.shields.io/aur/version/deepqt)](https://aur.archlinux.org/packages/deepqt/) 6 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 7 | 8 | Harness the power of the DeepL API with this friendly user interface. 9 | 10 | ![](/media/example_screenshot.png) 11 | 12 | ## Features 13 | 14 | - Batch processing. 15 | 16 | - Higher-level glossaries. [How do they work?](https://github.com/VoxelCubes/DeepQt/blob/master/docs/glossary_help.md) 17 | Apply replacements even without translating any of the text. 18 | 19 | - Support for plain text and epub files. 20 | 21 | ## Requires 22 | 23 | - A DeepL API license, either free or pro. [How do I get this?](https://github.com/VoxelCubes/DeepQt/blob/master/docs/api_help.md) 24 | 25 | - A Python installation, version **3.10** or higher. On Linux you will probably already have this installed. On Windows, you will likely need to set it up. [Download Python | Python.org](https://www.python.org/downloads/) or use your package manager (apt, pacman, dnf, chocolatey, winget etc.) 26 | 27 | ## Installation 28 | 29 | Download the latest release from [PyPI](https://pypi.org/project/deepqt/). 30 | 31 | ``` 32 | pip install deepqt 33 | ``` 34 | 35 | Or on Linux you can comfortably install it from [Flathub](https://flathub.org/apps/io.github.voxelcubes.deepqt) 36 | allowing you to launch it without the terminal. 37 | 38 | Or on Arch Linux (and derivatives) via the [AUR](https://aur.archlinux.org/packages/deepqt/). 39 | 40 | ``` 41 | yay -S deepqt 42 | ``` 43 | 44 | Run it with `deepqt` from the terminal if installed with pip, or like any other application if installed another way. 45 | 46 | Or download a prepackaged format from the [releases](https://github.com/VoxelCubes/DeepQt/releases/latest) page: 47 | 48 | - .whl (Manual pip installation) 49 | - .elf (Linux) 50 | - .exe (Windows) 51 | -------------------------------------------------------------------------------- /build-pyinstaller.bat: -------------------------------------------------------------------------------- 1 | :: Perform a Windows build. 2 | pyinstaller deepqt/main.py --onefile --noconfirm --clean --workpath=build --distpath=dist --windowed --name=DeepQt.exe --icon=media\logo.ico -------------------------------------------------------------------------------- /deepqt/CustomQ/CComboBox.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import PySide6.QtWidgets as Qw 4 | 5 | 6 | class CComboBox(Qw.QComboBox): 7 | """ 8 | Extends the functionality with custom helpers 9 | And includes a secondary array for data linked to each item 10 | """ 11 | 12 | def __init__(self, parent=None) -> None: 13 | Qw.QComboBox.__init__(self, parent) 14 | self._linked_data = [] 15 | 16 | def clear(self) -> None: 17 | Qw.QComboBox.clear(self) 18 | self._linked_data.clear() 19 | 20 | def addTextItemLinkedData(self, text: str, data: Any) -> None: 21 | self.addItem(text) 22 | self._linked_data.append(data) 23 | 24 | def setCurrentIndexByLinkedData(self, data: Any) -> None: 25 | self.setCurrentIndex(self._linked_data.index(data)) 26 | 27 | def indexLinkedData(self, data: Any) -> int: 28 | return self._linked_data.index(data) 29 | 30 | def currentLinkedData(self) -> None: 31 | if self.currentIndex() == -1: 32 | return None 33 | return self._linked_data[self.currentIndex()] 34 | -------------------------------------------------------------------------------- /deepqt/CustomQ/CDropFrame.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtGui as Qg 2 | import PySide6.QtWidgets as Qw 3 | from PySide6.QtCore import Signal 4 | 5 | 6 | # noinspection PyPep8Naming 7 | class CDropFrame(Qw.QFrame): 8 | """ 9 | Extends the functionality with custom helpers 10 | And includes a secondary array for data linked to each item 11 | """ 12 | 13 | drop_signal = Signal(Qg.QDropEvent) 14 | 15 | def __init__(self, parent=None) -> None: 16 | Qw.QFrame.__init__(self, parent) 17 | self.setAcceptDrops(True) 18 | 19 | def dragEnterEvent(self, event: Qg.QDragEnterEvent) -> None: 20 | if event.mimeData().hasUrls(): 21 | event.acceptProposedAction() 22 | 23 | def dropEvent(self, event: Qg.QDropEvent) -> None: 24 | self.drop_signal.emit(event) 25 | -------------------------------------------------------------------------------- /deepqt/CustomQ/CListWidget.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import PySide6.QtWidgets as Qw 4 | import PySide6.QtGui as Qg 5 | 6 | 7 | class CListWidget(Qw.QListWidget): 8 | """ 9 | Extends the functionality with custom helpers 10 | And includes a secondary array for data linked to each item 11 | """ 12 | 13 | def __init__(self, parent=None) -> None: 14 | Qw.QListWidget.__init__(self, parent) 15 | self._linked_data = [] 16 | 17 | def clear(self) -> None: 18 | Qw.QListWidget.clear(self) 19 | self._linked_data.clear() 20 | 21 | def addTextItemLinkedData(self, text: str, data: Any) -> None: 22 | self.addItem(text) 23 | self._linked_data.append(data) 24 | 25 | def addIconTextItemLinkedData(self, icon: Qg.QIcon, text: str, data: Any) -> None: 26 | item = Qw.QListWidgetItem(icon, text) 27 | self.addItem(item) 28 | self._linked_data.append(data) 29 | 30 | def setCurrentIndexByLinkedData(self, data: Any) -> None: 31 | self.setCurrentRow(self._linked_data.index(data)) 32 | 33 | def indexLinkedData(self, data: Any) -> int: 34 | return self._linked_data.index(data) 35 | 36 | def currentLinkedData(self) -> Any: 37 | if self.currentRow() == -1: 38 | return None 39 | return self._linked_data[self.currentRow()] 40 | -------------------------------------------------------------------------------- /deepqt/CustomQ/CTableWidget.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtWidgets as Qw 2 | import PySide6.QtGui as Qg 3 | import PySide6.QtCore as Qc 4 | 5 | 6 | class CTableWidget(Qw.QTableWidget): 7 | """ 8 | Extends the functionality with custom helpers 9 | """ 10 | 11 | finished_drop = Qc.Signal() 12 | 13 | def __init__(self, parent=None) -> None: 14 | Qw.QTableWidget.__init__(self, parent) 15 | 16 | def clearAll(self) -> None: 17 | self.clearContents() 18 | self.setRowCount(0) 19 | self.itemSelectionChanged.emit() 20 | 21 | def currentText(self, col: int) -> None: 22 | return self.item(self.currentRow(), col).text() 23 | 24 | def setCurrentText(self, col: int, text: str) -> None: 25 | return self.item(self.currentRow(), col).setText(text) 26 | 27 | def appendRow(self, *args: str, select_new: bool = False) -> None: 28 | """ 29 | Adds a new row to the bottom and fills each column with one of the args 30 | :param args: list(str) 31 | :param select_new: bool - highlight the new row 32 | """ 33 | rows = self.rowCount() 34 | self.setRowCount(rows + 1) 35 | for i, arg in enumerate(args): 36 | self.setItem(rows, i, Qw.QTableWidgetItem(arg)) 37 | if select_new: 38 | self.setCurrentCell(rows, 0) 39 | 40 | def moveRowUp(self, row: int) -> None: 41 | """ 42 | Inserts a new row above row, then moves all items into it. 43 | Then deletes old row and selects new row. 44 | :param row: int 45 | """ 46 | self.insertRow(row - 1) 47 | for i in range(self.columnCount()): 48 | item = self.takeItem(row + 1, i) 49 | self.setItem(row - 1, i, item) 50 | self.removeRow(row + 1) 51 | self.setCurrentCell(row - 1, 0) 52 | 53 | def moveRowDown(self, row: int) -> None: 54 | """ 55 | Inserts a new row below row, then moves all items into it. 56 | Then deletes old row and selects new row. 57 | :param row: int 58 | """ 59 | self.insertRow(row + 2) 60 | for i in range(self.columnCount()): 61 | item = self.takeItem(row, i) 62 | self.setItem(row + 2, i, item) 63 | self.removeRow(row) 64 | self.setCurrentCell(row + 1, 0) 65 | 66 | def columnValues(self, col: int) -> None: 67 | values = [] 68 | for row in range(self.rowCount()): 69 | values.append(self.item(row, col).text()) 70 | return values 71 | 72 | def resizeHeightToContents(self) -> None: 73 | # Add 1 per row to compensate for grid lines. Subtract 1 at the end since only inner grid lines count. 74 | height = 0 75 | for i in range(self.rowCount()): 76 | height += self.rowHeight(i) + 1 77 | self.setMinimumHeight(height - 1) 78 | 79 | def hasSelected(self) -> None: 80 | return self.selectedIndexes() != [] 81 | 82 | def dragEnterEvent(self, event: Qg.QDragEnterEvent) -> None: 83 | if event.mimeData().hasUrls(): 84 | event.accept() 85 | else: 86 | event.ignore() 87 | 88 | def dragMoveEvent(self, event: Qg.QDragMoveEvent) -> None: 89 | if event.mimeData().hasUrls(): 90 | event.accept() 91 | else: 92 | event.ignore() 93 | 94 | def dropEvent(self, event: Qg.QDropEvent) -> None: 95 | if event.mimeData().hasUrls(): 96 | for url in event.mimeData().urls(): 97 | self.handleDrop(url.toLocalFile()) 98 | event.accept() 99 | else: 100 | event.ignore() 101 | self.finished_drop.emit() 102 | 103 | def handleDrop(self, path: str) -> None: 104 | pass 105 | -------------------------------------------------------------------------------- /deepqt/CustomQ/CTextEdit.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtWidgets as Qw 2 | from PySide6.QtCore import Qt 3 | import PySide6.QtGui as Qg 4 | 5 | 6 | # noinspection PyPep8Naming 7 | class CTextEdit(Qw.QTextEdit): 8 | """ 9 | A QTextEdit with a few extra features. 10 | - A context menu option to clear the text. 11 | """ 12 | 13 | def __init__(self, parent=None) -> None: 14 | Qw.QTextEdit.__init__(self, parent) 15 | 16 | # Create a custom action for clearing the text 17 | self.clear_action = Qg.QAction(self.tr("Clear\tCtrl+L"), self) 18 | self.clear_action.setIcon(Qg.QIcon.fromTheme("edit-clear-history")) 19 | self.clear_action.triggered.connect(self.clear) 20 | 21 | # Create a shortcut for the clear action 22 | self.clear_shortcut = Qg.QShortcut(Qg.QKeySequence("Ctrl+L"), self) 23 | self.clear_shortcut.setContext(Qt.WidgetWithChildrenShortcut) 24 | self.clear_shortcut.activated.connect(self.clear) 25 | 26 | def contextMenuEvent(self, event) -> None: 27 | menu = self.createStandardContextMenu() 28 | 29 | menu.addAction(self.clear_action) 30 | 31 | # If the text is empty, disable the clear action. 32 | self.clear_action.setEnabled(bool(self.toPlainText())) 33 | 34 | menu.exec_(event.globalPos()) 35 | -------------------------------------------------------------------------------- /deepqt/CustomQ/CTooltipLabel.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtWidgets as Qw 2 | import PySide6.QtGui as Qg 3 | import PySide6.QtCore as Qc 4 | from PySide6.QtCore import Qt 5 | 6 | from loguru import logger 7 | 8 | 9 | class CTooltipLabel(Qw.QLabel): 10 | """ 11 | A label that displays a help-hint icon and shows a tooltip when clicked. 12 | This is to make little helper icons more consistent. 13 | """ 14 | 15 | icon_name: str 16 | 17 | def __init__(self, parent=None, tooltip: str = "", icon_name: str = "help-hint") -> None: 18 | super(CTooltipLabel, self).__init__(parent) 19 | 20 | # Display the help-hint icon and no text. 21 | self.icon_name = icon_name 22 | self.load_icon() 23 | self.setText("") 24 | if tooltip: 25 | self.setToolTip(tooltip) 26 | 27 | # Make the label focusable to receive keyboard events 28 | self.setFocusPolicy(Qt.StrongFocus) 29 | 30 | def changeEvent(self, event) -> None: 31 | if event.type() == Qc.QEvent.PaletteChange: 32 | self.load_icon() 33 | 34 | def load_icon(self) -> None: 35 | """ 36 | Load the display icon. 37 | """ 38 | if Qg.QIcon.hasThemeIcon(self.icon_name): 39 | self.setPixmap(Qg.QIcon.fromTheme(self.icon_name).pixmap(16, 16)) 40 | else: 41 | logger.error(f"Icon '{self.icon_name}' not found in the current icon theme.") 42 | 43 | def setText(self, text: str) -> None: 44 | """ 45 | Ignore all attempts to set the text. 46 | """ 47 | pass 48 | 49 | def keyPressEvent(self, event) -> None: 50 | """ 51 | Show tooltip when the Enter or Space key is pressed. 52 | """ 53 | if event.key() in (Qt.Key_Return, Qt.Key_Space): 54 | # Showing tooltip at the center of the label when triggered by keyboard. 55 | self.showTooltipAtPosition(self.rect().center()) 56 | 57 | def showTooltipAtPosition(self, pos: Qc.QPoint) -> None: 58 | """ 59 | Show the tooltip at the given local position. 60 | """ 61 | global_pos = self.mapToGlobal(pos) 62 | Qw.QToolTip.showText(global_pos, self.toolTip()) 63 | 64 | def mousePressEvent(self, event) -> None: 65 | """ 66 | Show tooltip on mouse click. 67 | """ 68 | box = Qw.QMessageBox(Qw.QMessageBox.Information, "Hint", self.toolTip(), Qw.QMessageBox.Ok) 69 | box.exec() 70 | -------------------------------------------------------------------------------- /deepqt/CustomQ/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/CustomQ/__init__.py -------------------------------------------------------------------------------- /deepqt/__init__.py: -------------------------------------------------------------------------------- 1 | __program__ = "deepqt" 2 | __display_name__ = "DeepQt" 3 | __version__ = "1.9.0" 4 | __description__ = "Translation API front-end" 5 | -------------------------------------------------------------------------------- /deepqt/backends/__init__.py: -------------------------------------------------------------------------------- 1 | import deepqt.backends.backend_interface as bi 2 | import deepqt.backends.deepl_backend as db 3 | import deepqt.backends.mock_backend as mb 4 | import deepqt.constants as ct 5 | 6 | 7 | def get_backend(backend: ct.Backend) -> bi.ReliableBackend | bi.UnreliableBackend: 8 | if backend == ct.Backend.MOCK: 9 | return mb.MockBackend() 10 | elif backend == ct.Backend.DEEPL: 11 | return db.DeepLBackend() 12 | else: 13 | raise ValueError(f"Backend {backend} not supported") 14 | -------------------------------------------------------------------------------- /deepqt/backends/lookups.py: -------------------------------------------------------------------------------- 1 | import deepqt.constants as ct 2 | import deepqt.backends.mock_backend as mb 3 | import deepqt.backends.deepl_backend as db 4 | 5 | # This is a mapping of backend names to their respective classes. 6 | # File separated out to prevent circular imports. 7 | 8 | backend_to_config = { 9 | ct.Backend.MOCK: mb.MockConfig, 10 | ct.Backend.DEEPL: db.DeepLConfig, 11 | } 12 | 13 | backend_to_class = { 14 | ct.Backend.MOCK: mb.MockBackend, 15 | ct.Backend.DEEPL: db.DeepLBackend, 16 | } 17 | -------------------------------------------------------------------------------- /deepqt/constants.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, StrEnum, auto 2 | from typing import Sequence, NewType 3 | 4 | from attrs import frozen 5 | 6 | # Create a new type for percentages as floats. These are between 0 and 100. 7 | Percentage = NewType("Percentage", float) 8 | 9 | Milliseconds = NewType("Milliseconds", int) 10 | 11 | Seconds = NewType("Seconds", int) 12 | 13 | SecondsF = NewType("Seconds", float) 14 | 15 | APIKey = NewType("APIKey", str) 16 | 17 | HTML = NewType("HTML", str) 18 | 19 | 20 | class TranslationMode(StrEnum): 21 | Text = "Text" 22 | File = "File" 23 | 24 | 25 | class Command(Enum): 26 | NONE = None 27 | FILES = "files" 28 | TEXT = "text" 29 | CLIPBOARD = "clipboard" 30 | 31 | 32 | class Backend(StrEnum): 33 | MOCK = "MOCK" # Mocking a reliable backend. 34 | # MOCK_LLM = "MOCK_LLM" # Mocking an unreliable backend. 35 | DEEPL = "DEEPL" 36 | 37 | 38 | # debug_backends = [Backend.MOCK, Backend.MOCK_LLM] 39 | debug_backends = [Backend.MOCK] 40 | 41 | 42 | @frozen 43 | class FileType: 44 | name: str 45 | extensions: Sequence[str] 46 | mimetype: str 47 | 48 | 49 | class Formats(Enum): 50 | TEXT = FileType("Plain text", (".txt",), "text/plain") 51 | EPUB = FileType("EPUB", (".epub",), "application/epub+zip") 52 | PDF = FileType("PDF", (".pdf",), "application/pdf") 53 | DOCX = FileType( 54 | "Word", 55 | (".docx",), 56 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 57 | ) 58 | ODT = FileType("OpenDocument", (".odt",), "application/vnd.oasis.opendocument.text") 59 | PPTX = FileType( 60 | "PowerPoint", 61 | (".pptx",), 62 | "application/vnd.openxmlformats-officedocument.presentationml.presentation", 63 | ) 64 | ODP = FileType( 65 | "OpenDocument Presentation", (".odp",), "application/vnd.oasis.opendocument.presentation" 66 | ) 67 | XLSX = FileType( 68 | "Excel", (".xlsx",), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 69 | ) 70 | ODS = FileType( 71 | "OpenDocument Spreadsheet", (".ods",), "application/vnd.oasis.opendocument.spreadsheet" 72 | ) 73 | HTML = FileType("HTML", (".html",), "text/html") 74 | XLF = FileType( 75 | "XLIFF", (".xlf", ".xliff"), "application/x-xliff+xml" 76 | ) # May be recognized as just XML. 77 | UNKNOWN = FileType("Unknown", (), "") 78 | 79 | 80 | # Reverse indexes. 81 | mime_to_format = {fmt.value.mimetype: fmt for fmt in Formats} 82 | extension_to_format = {ext: fmt for fmt in Formats for ext in fmt.value.extensions} 83 | -------------------------------------------------------------------------------- /deepqt/data/LiberationSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/LiberationSans-Regular.ttf -------------------------------------------------------------------------------- /deepqt/data/NotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/NotoMono-Regular.ttf -------------------------------------------------------------------------------- /deepqt/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/__init__.py -------------------------------------------------------------------------------- /deepqt/data/color_themes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/color_themes/__init__.py -------------------------------------------------------------------------------- /deepqt/data/color_themes/breeze: -------------------------------------------------------------------------------- 1 | [ColorEffects:Disabled] 2 | Color=56,56,56 3 | ColorAmount=0 4 | ColorEffect=0 5 | ContrastAmount=0.65 6 | ContrastEffect=1 7 | IntensityAmount=0.1 8 | IntensityEffect=2 9 | 10 | [ColorEffects:Inactive] 11 | ChangeSelectionColor=true 12 | Color=112,111,110 13 | ColorAmount=0.025 14 | ColorEffect=2 15 | ContrastAmount=0.1 16 | ContrastEffect=2 17 | Enable=false 18 | IntensityAmount=0 19 | IntensityEffect=0 20 | 21 | [Colors:Button] 22 | BackgroundAlternate=163,212,250 23 | BackgroundNormal=247,247,247 24 | DecorationFocus=61,174,233 25 | DecorationHover=61,174,233 26 | ForegroundActive=61,174,233 27 | ForegroundInactive=112,125,138 28 | ForegroundLink=41,128,185 29 | ForegroundNegative=218,68,83 30 | ForegroundNeutral=246,116,0 31 | ForegroundNormal=35,38,41 32 | ForegroundPositive=39,174,96 33 | ForegroundVisited=155,89,182 34 | 35 | [Colors:Complementary] 36 | BackgroundAlternate=27,30,32 37 | BackgroundNormal=42,46,50 38 | DecorationFocus=61,174,233 39 | DecorationHover=61,174,233 40 | ForegroundActive=61,174,233 41 | ForegroundInactive=161,169,177 42 | ForegroundLink=29,153,243 43 | ForegroundNegative=218,68,83 44 | ForegroundNeutral=246,116,0 45 | ForegroundNormal=252,252,252 46 | ForegroundPositive=39,174,96 47 | ForegroundVisited=155,89,182 48 | 49 | [Colors:Header] 50 | BackgroundAlternate=239,240,241 51 | BackgroundNormal=222,224,226 52 | DecorationFocus=61,174,233 53 | DecorationHover=61,174,233 54 | ForegroundActive=61,174,233 55 | ForegroundInactive=112,125,138 56 | ForegroundLink=41,128,185 57 | ForegroundNegative=218,68,83 58 | ForegroundNeutral=246,116,0 59 | ForegroundNormal=35,38,41 60 | ForegroundPositive=39,174,96 61 | ForegroundVisited=155,89,182 62 | 63 | [Colors:Header][Inactive] 64 | BackgroundAlternate=227,229,231 65 | BackgroundNormal=239,240,241 66 | DecorationFocus=61,174,233 67 | DecorationHover=61,174,233 68 | ForegroundActive=61,174,233 69 | ForegroundInactive=112,125,138 70 | ForegroundLink=41,128,185 71 | ForegroundNegative=218,68,83 72 | ForegroundNeutral=246,116,0 73 | ForegroundNormal=35,38,41 74 | ForegroundPositive=39,174,96 75 | ForegroundVisited=155,89,182 76 | 77 | [Colors:Selection] 78 | BackgroundAlternate=163,212,250 79 | BackgroundNormal=61,174,233 80 | DecorationFocus=61,174,233 81 | DecorationHover=61,174,233 82 | ForegroundActive=61,174,233 83 | ForegroundInactive=112,125,138 84 | ForegroundLink=41,128,185 85 | ForegroundNegative=218,68,83 86 | ForegroundNeutral=246,116,0 87 | ForegroundNormal=255,255,255 88 | ForegroundPositive=39,174,96 89 | ForegroundVisited=155,89,182 90 | 91 | [Colors:Tooltip] 92 | BackgroundAlternate=239,240,241 93 | BackgroundNormal=247,247,247 94 | DecorationFocus=61,174,233 95 | DecorationHover=61,174,233 96 | ForegroundActive=61,174,233 97 | ForegroundInactive=112,125,138 98 | ForegroundLink=41,128,185 99 | ForegroundNegative=218,68,83 100 | ForegroundNeutral=246,116,0 101 | ForegroundNormal=35,38,41 102 | ForegroundPositive=39,174,96 103 | ForegroundVisited=155,89,182 104 | 105 | [Colors:View] 106 | BackgroundAlternate=247,247,247 107 | BackgroundNormal=255,255,255 108 | DecorationFocus=61,174,233 109 | DecorationHover=61,174,233 110 | ForegroundActive=61,174,233 111 | ForegroundInactive=112,125,138 112 | ForegroundLink=41,128,185 113 | ForegroundNegative=218,68,83 114 | ForegroundNeutral=246,116,0 115 | ForegroundNormal=35,38,41 116 | ForegroundPositive=39,174,96 117 | ForegroundVisited=155,89,182 118 | 119 | [Colors:Window] 120 | BackgroundAlternate=227,229,231 121 | BackgroundNormal=239,240,241 122 | DecorationFocus=61,174,233 123 | DecorationHover=61,174,233 124 | ForegroundActive=61,174,233 125 | ForegroundInactive=112,125,138 126 | ForegroundLink=41,128,185 127 | ForegroundNegative=218,68,83 128 | ForegroundNeutral=246,116,0 129 | ForegroundNormal=35,38,41 130 | ForegroundPositive=39,174,96 131 | ForegroundVisited=155,89,182 132 | 133 | [General] 134 | ColorScheme=Breeze Light 135 | Name=Breeze Light 136 | shadeSortColumn=true 137 | 138 | [KDE] 139 | contrast=4 140 | 141 | [WM] 142 | activeBackground=227,229,231 143 | activeBlend=227,229,231 144 | activeForeground=35,38,41 145 | inactiveBackground=239,240,241 146 | inactiveBlend=239,240,241 147 | inactiveForeground=112,125,138 148 | -------------------------------------------------------------------------------- /deepqt/data/color_themes/breeze-dark: -------------------------------------------------------------------------------- 1 | [ColorEffects:Disabled] 2 | Color=56,56,56 3 | ColorAmount=0 4 | ColorEffect=0 5 | ContrastAmount=0.65 6 | ContrastEffect=1 7 | IntensityAmount=0.1 8 | IntensityEffect=2 9 | 10 | [ColorEffects:Inactive] 11 | ChangeSelectionColor=true 12 | Color=112,111,110 13 | ColorAmount=0.025 14 | ColorEffect=2 15 | ContrastAmount=0.1 16 | ContrastEffect=2 17 | Enable=false 18 | IntensityAmount=0 19 | IntensityEffect=0 20 | 21 | [Colors:Button] 22 | BackgroundAlternate=30,87,116 23 | BackgroundNormal=49,54,59 24 | DecorationFocus=61,174,233 25 | DecorationHover=61,174,233 26 | ForegroundActive=61,174,233 27 | ForegroundInactive=161,169,177 28 | ForegroundLink=29,153,243 29 | ForegroundNegative=218,68,83 30 | ForegroundNeutral=246,116,0 31 | ForegroundNormal=252,252,252 32 | ForegroundPositive=39,174,96 33 | ForegroundVisited=155,89,182 34 | 35 | [Colors:Complementary] 36 | BackgroundAlternate=30,87,116 37 | BackgroundNormal=42,46,50 38 | DecorationFocus=61,174,233 39 | DecorationHover=61,174,233 40 | ForegroundActive=61,174,233 41 | ForegroundInactive=161,169,177 42 | ForegroundLink=29,153,243 43 | ForegroundNegative=218,68,83 44 | ForegroundNeutral=246,116,0 45 | ForegroundNormal=252,252,252 46 | ForegroundPositive=39,174,96 47 | ForegroundVisited=155,89,182 48 | 49 | [Colors:Header] 50 | BackgroundAlternate=42,46,50 51 | BackgroundNormal=49,54,59 52 | DecorationFocus=61,174,233 53 | DecorationHover=61,174,233 54 | ForegroundActive=61,174,233 55 | ForegroundInactive=161,169,177 56 | ForegroundLink=29,153,243 57 | ForegroundNegative=218,68,83 58 | ForegroundNeutral=246,116,0 59 | ForegroundNormal=252,252,252 60 | ForegroundPositive=39,174,96 61 | ForegroundVisited=155,89,182 62 | 63 | [Colors:Header][Inactive] 64 | BackgroundAlternate=49,54,59 65 | BackgroundNormal=42,46,50 66 | DecorationFocus=61,174,233 67 | DecorationHover=61,174,233 68 | ForegroundActive=61,174,233 69 | ForegroundInactive=161,169,177 70 | ForegroundLink=29,153,243 71 | ForegroundNegative=218,68,83 72 | ForegroundNeutral=246,116,0 73 | ForegroundNormal=252,252,252 74 | ForegroundPositive=39,174,96 75 | ForegroundVisited=155,89,182 76 | 77 | [Colors:Selection] 78 | BackgroundAlternate=30,87,116 79 | BackgroundNormal=61,174,233 80 | DecorationFocus=61,174,233 81 | DecorationHover=61,174,233 82 | ForegroundActive=252,252,252 83 | ForegroundInactive=161,169,177 84 | ForegroundLink=253,188,75 85 | ForegroundNegative=218,68,83 86 | ForegroundNeutral=246,116,0 87 | ForegroundNormal=252,252,252 88 | ForegroundPositive=39,174,96 89 | ForegroundVisited=155,89,182 90 | 91 | [Colors:Tooltip] 92 | BackgroundAlternate=42,46,50 93 | BackgroundNormal=49,54,59 94 | DecorationFocus=61,174,233 95 | DecorationHover=61,174,233 96 | ForegroundActive=61,174,233 97 | ForegroundInactive=161,169,177 98 | ForegroundLink=29,153,243 99 | ForegroundNegative=218,68,83 100 | ForegroundNeutral=246,116,0 101 | ForegroundNormal=252,252,252 102 | ForegroundPositive=39,174,96 103 | ForegroundVisited=155,89,182 104 | 105 | [Colors:View] 106 | BackgroundAlternate=35,38,41 107 | BackgroundNormal=27,30,32 108 | DecorationFocus=61,174,233 109 | DecorationHover=61,174,233 110 | ForegroundActive=61,174,233 111 | ForegroundInactive=161,169,177 112 | ForegroundLink=29,153,243 113 | ForegroundNegative=218,68,83 114 | ForegroundNeutral=246,116,0 115 | ForegroundNormal=252,252,252 116 | ForegroundPositive=39,174,96 117 | ForegroundVisited=155,89,182 118 | 119 | [Colors:Window] 120 | BackgroundAlternate=49,54,59 121 | BackgroundNormal=42,46,50 122 | DecorationFocus=61,174,233 123 | DecorationHover=61,174,233 124 | ForegroundActive=61,174,233 125 | ForegroundInactive=161,169,177 126 | ForegroundLink=29,153,243 127 | ForegroundNegative=218,68,83 128 | ForegroundNeutral=246,116,0 129 | ForegroundNormal=252,252,252 130 | ForegroundPositive=39,174,96 131 | ForegroundVisited=155,89,182 132 | 133 | [General] 134 | ColorScheme=Breeze Dark 135 | Name=Breeze Dark 136 | shadeSortColumn=true 137 | 138 | [KDE] 139 | contrast=4 140 | 141 | [WM] 142 | activeBackground=49,54,59 143 | activeBlend=252,252,252 144 | activeForeground=252,252,252 145 | inactiveBackground=42,46,50 146 | inactiveBlend=161,169,177 147 | inactiveForeground=161,169,177 148 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/custom_icons/__init__.py -------------------------------------------------------------------------------- /deepqt/data/custom_icons/dark/cost-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 30 | 32 | 39 | 40 | 45 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/dark/unreliable-service.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 30 | 32 | 39 | 40 | 43 | 46 | 52 | 53 | 54 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/deepl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/custom_icons/deepl.png -------------------------------------------------------------------------------- /deepqt/data/custom_icons/generic-backend.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 30 | 32 | 49 | 56 | 57 | 67 | 72 | 73 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 43 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/light/cost-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 30 | 32 | 39 | 40 | 45 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/light/unreliable-service.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 30 | 32 | 39 | 40 | 43 | 46 | 52 | 53 | 54 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /deepqt/data/custom_icons/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/custom_icons/logo.ico -------------------------------------------------------------------------------- /deepqt/data/theme_icons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/data/theme_icons/__init__.py -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/application-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/configure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/dialog-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/dialog-ok-apply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/dialog-ok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/document-multiple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/document-open-folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/document-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/document-preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/document-save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/edit-clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/edit-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/edit-delete-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/edit-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/games-config-theme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/help-contents.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/help-hint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/list-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/media-playback-start.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/process-stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/tools-report-bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/view-list-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/view-refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/actions/16/window-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/mimetypes/16/text-x-changelog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/mimetypes/32/application-epub+zip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/mimetypes/32/text-x-generic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/data-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/data-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/dialog-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/image-missing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/state-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/state-offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/16/state-ok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze-dark/status/22/dialog-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/application-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/configure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/dialog-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/dialog-ok-apply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/dialog-ok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/document-multiple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/document-open-folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/document-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/document-preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/document-save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/edit-clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/edit-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/edit-delete-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/edit-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/games-config-theme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/help-contents.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/help-hint.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/list-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/media-playback-start.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/process-stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/tools-report-bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/view-list-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/view-refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/actions/16/window-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/mimetypes/16/text-x-changelog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/mimetypes/32/application-epub+zip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/mimetypes/32/text-x-generic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/data-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/data-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/dialog-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/image-missing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/state-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 14 | 20 | 25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/state-offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 17 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/16/state-ok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 14 | 19 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /deepqt/data/theme_icons/breeze/status/22/dialog-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /deepqt/driver_api_key_input.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtWidgets as Qw 2 | 3 | import deepqt.constants as ct 4 | from deepqt.ui_generated_files.ui_api_key_input import Ui_Dialog_API 5 | from deepqt.gui_utils import show_info 6 | 7 | 8 | class KeyInputDialog(Qw.QDialog, Ui_Dialog_API): 9 | """ 10 | A little dialog to enter the API key. 11 | This way the API key is normally hidden from the user. 12 | """ 13 | 14 | key: ct.APIKey 15 | help_html: ct.HTML 16 | 17 | def __init__(self, parent, key: ct.APIKey, help_html: ct.HTML) -> None: 18 | # Don't pass the parent due to a bug in PySide6. 19 | Qw.QDialog.__init__(self) 20 | self.setupUi(self) 21 | 22 | self.lineEdit_api_key.setText(key) 23 | self.lineEdit_api_key.setFocus() 24 | 25 | self.key = key 26 | self.help_html = help_html 27 | 28 | if help_html: 29 | self.buttonBox.helpRequested.connect(self.show_api_help) 30 | else: 31 | # Hide the help button. 32 | self.buttonBox.button(Qw.QDialogButtonBox.Help).hide() 33 | 34 | def get_key(self) -> ct.APIKey: 35 | return ct.APIKey(self.lineEdit_api_key.text().strip()) 36 | 37 | def show_api_help(self) -> None: 38 | show_info(self, "API Help", self.help_html) 39 | -------------------------------------------------------------------------------- /deepqt/driver_text_preview.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtWidgets as Qw 2 | 3 | import deepqt.config as cfg 4 | import deepqt.structures as st 5 | from deepqt.ui_generated_files.ui_text_preview import Ui_TextPreview 6 | 7 | 8 | class TextPreview(Qw.QDialog, Ui_TextPreview): 9 | """ 10 | Preview text files with and without glossaries/quote protection applied. 11 | Also offer to save the previews to a file. 12 | """ 13 | 14 | config: cfg.Config 15 | text_file: st.TextFile 16 | 17 | def __init__(self, parent, text_file: st.TextFile, config: cfg.Config) -> None: 18 | # Don't pass the parent due to a bug in PySide6. 19 | Qw.QDialog.__init__(self) 20 | self.setupUi(self) 21 | self.setWindowTitle(f"{text_file.path.name} - Preview") 22 | 23 | self.config = config 24 | self.text_file = text_file 25 | 26 | self.preview_text() 27 | 28 | self.pushButton_save.clicked.connect(self.save_preview) 29 | 30 | def preview_text(self) -> None: 31 | """ 32 | Determine how many previews to generate and show each in a tab. 33 | """ 34 | self.add_preview("Original", self.text_file.text) 35 | 36 | if self.text_file.process_level & st.ProcessLevel.GLOSSARY: 37 | self.add_preview("Glossary", self.text_file.text_glossary) 38 | 39 | if self.text_file.process_level == st.ProcessLevel.PROTECTED: 40 | self.add_preview("Protected", self.text_file.text_protected) 41 | elif self.text_file.process_level == st.ProcessLevel.GLOSSARY_PROTECTED: 42 | self.add_preview("Glossary Protected", self.text_file.text_glossary_protected) 43 | 44 | if self.text_file.translation: 45 | self.add_preview("Translation", self.text_file.translation) 46 | 47 | def add_preview(self, title: str, text: str) -> None: 48 | """ 49 | Add a preview tab to the dialog. 50 | Show the text in a QPlainTextEdit set to read only mode. 51 | Also show line numbers. 52 | """ 53 | preview_tab = Qw.QWidget() 54 | preview_tab.setObjectName(title) 55 | preview_layout = Qw.QVBoxLayout(preview_tab) 56 | preview_layout.setContentsMargins(0, 0, 0, 0) 57 | preview_layout.setSpacing(0) 58 | preview_layout.setObjectName("preview_layout") 59 | preview_text = Qw.QPlainTextEdit(preview_tab) 60 | preview_text.setReadOnly(True) 61 | preview_text.setObjectName("preview_text") 62 | preview_text.setPlainText(text) 63 | preview_layout.addWidget(preview_text) 64 | self.tabWidget.addTab(preview_tab, title) 65 | 66 | def save_preview(self) -> None: 67 | """ 68 | Save the preview in the currently visible QPlainTextEdit to a file. 69 | """ 70 | preview_text = self.tabWidget.currentWidget().findChild(Qw.QPlainTextEdit, "preview_text") 71 | save_path = self.text_file.path.with_stem( 72 | self.text_file.path.stem 73 | + "_" 74 | + self.tabWidget.tabText(self.tabWidget.currentIndex()).replace(" ", "_") 75 | ) 76 | file_path = Qw.QFileDialog.getSaveFileName( 77 | self, 78 | "Save Preview", 79 | str(save_path), 80 | "Text Files (*.txt)", 81 | )[0] 82 | if file_path: 83 | try: 84 | with open(file_path, "w", encoding="utf8") as f: 85 | f.write(preview_text.toPlainText()) 86 | except OSError as e: 87 | Qw.QMessageBox.warning( 88 | self, "Error", f"Could not save preview to {file_path}\n\n{e}" 89 | ) 90 | -------------------------------------------------------------------------------- /deepqt/error_dialog_driver.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtCore as Qc 2 | import PySide6.QtGui as Qg 3 | import PySide6.QtWidgets as Qw 4 | from loguru import logger 5 | 6 | import deepqt.log_parser as lp 7 | import deepqt.state_saver as ss 8 | from deepqt.ui_generated_files.ui_ErrorDialog import Ui_ErrorDialog 9 | 10 | 11 | class ErrorDialog(Qw.QDialog, Ui_ErrorDialog): 12 | """ 13 | Show logs of individual sessions and allow the user to report an issue. 14 | """ 15 | 16 | session_log: lp.LogSession 17 | 18 | def __init__( 19 | self, 20 | parent=None, 21 | title: str = "Error", 22 | message: str = "An error occurred.", 23 | ) -> None: 24 | """ 25 | Init the widget. 26 | 27 | :param parent: The parent widget. 28 | """ 29 | Qw.QDialog.__init__(self, parent) 30 | self.setupUi(self) 31 | 32 | self.label_error_icon.setPixmap(Qg.QIcon.fromTheme("dialog-error").pixmap(64, 64)) 33 | 34 | self.setWindowTitle(title) 35 | self.label_message.setText(message) 36 | 37 | self.pushButton_close.clicked.connect(self.close) 38 | self.pushButton_kill.clicked.connect(self.kill) 39 | self.pushButton_open_issues.clicked.connect(self.open_issues) 40 | self.pushButton_clipboard.clicked.connect(self.copy_to_clipboard) 41 | 42 | self.label_name_hidden.setText( 43 | self.tr('Note: Name "{name}" and API keys were hidden').format(name=lp.get_username()) 44 | ) 45 | self.load_session_log() 46 | 47 | self.state_saver = ss.StateSaver("error_dialog") 48 | self.init_state_saver() 49 | self.state_saver.restore() 50 | 51 | def closeEvent(self, event: Qg.QCloseEvent) -> None: 52 | """ 53 | Called when the window is closed. 54 | """ 55 | self.state_saver.save() 56 | event.accept() 57 | 58 | def init_state_saver(self) -> None: 59 | """ 60 | Load the state from the state saver. 61 | """ 62 | self.state_saver.register( 63 | self, 64 | ) 65 | 66 | @staticmethod 67 | def open_issues() -> None: 68 | """ 69 | Open the issues page in the browser. 70 | """ 71 | logger.debug("Opening github issues page.") 72 | Qg.QDesktopServices.openUrl(Qc.QUrl("https://github.com/VoxelCubes/DeepQt/issues")) 73 | 74 | def load_session_log(self) -> None: 75 | """ 76 | Load the logs. 77 | """ 78 | log_text = lp.load_log_file() 79 | if log_text is None: 80 | return 81 | 82 | sessions = lp.parse_log_file(log_text, max_sessions=1) 83 | if len(sessions) != 1: 84 | self.session_log = lp.LogSession("Failed to load log.") 85 | else: 86 | self.session_log = lp.parse_log_file(log_text, max_sessions=1)[0] 87 | 88 | self.log_viewer.show_log(self.session_log.text) 89 | self.log_viewer.scroll_to_bottom() 90 | 91 | def copy_to_clipboard(self) -> None: 92 | """ 93 | Copy the issue report to the clipboard. 94 | """ 95 | logger.debug("Copying issue report to clipboard.") 96 | Qg.QGuiApplication.clipboard().setText(self.session_log.text) 97 | 98 | def kill(self) -> None: 99 | """ 100 | Kill the application. 101 | This may be necessary when repeated errors occur and the application is unresponsive. 102 | """ 103 | logger.critical("User killed the application.") 104 | Qw.QApplication.instance().quit() # Embrace death. 105 | self.close() 106 | raise SystemExit(255) 107 | -------------------------------------------------------------------------------- /deepqt/issue_reporter_driver.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Sequence 3 | 4 | import PySide6.QtCore as Qc 5 | import PySide6.QtGui as Qg 6 | import PySide6.QtWidgets as Qw 7 | from loguru import logger 8 | 9 | import deepqt.log_parser as lp 10 | import deepqt.utils as ut 11 | from deepqt.ui_generated_files.ui_IssueReporter import Ui_IssueReporter 12 | 13 | 14 | class IssueReporter(Qw.QDialog, Ui_IssueReporter): 15 | """ 16 | Show logs of individual sessions and allow the user to report an issue. 17 | """ 18 | 19 | sessions: Sequence[lp.LogSession] 20 | 21 | def __init__( 22 | self, 23 | parent=None, 24 | ) -> None: 25 | """ 26 | Init the widget. 27 | 28 | :param parent: The parent widget. 29 | """ 30 | Qw.QDialog.__init__(self, parent) 31 | self.setupUi(self) 32 | 33 | self.pushButton_close.clicked.connect(self.close) 34 | self.pushButton_open_issues.clicked.connect(self.open_issues) 35 | self.pushButton_clipboard.clicked.connect(self.copy_to_clipboard) 36 | 37 | self.load_logs() 38 | # Only connect the signal after the logs are loaded to not double-trigger 39 | # the update when making the initial selection. 40 | self.comboBox_sessions.currentIndexChanged.connect(self.show_log) 41 | self.show_log() 42 | 43 | self.label_log_path.setText(str(ut.get_log_path())) 44 | self.label_name_hidden.setText( 45 | "Note: Name {name} and api keys were hidden".format(name=lp.get_username()) 46 | ) 47 | 48 | def load_logs(self) -> None: 49 | """ 50 | Load the logs. 51 | """ 52 | 53 | log_text = lp.load_log_file() 54 | if log_text is None: 55 | return 56 | 57 | self.sessions = lp.parse_log_file(log_text) 58 | 59 | for index, session in enumerate(self.sessions): 60 | text: str 61 | if session.corrupted: 62 | text = "Corrupted log session" 63 | elif index == 0: 64 | text = "Current session" 65 | else: 66 | today = datetime.now().date() 67 | if session.date_time.date() == today: 68 | text = "Today" + session.date_time.strftime(" %H:%M:%S") 69 | else: 70 | text = session.date_time.strftime("%Y-%m-%d %H:%M:%S") 71 | 72 | if session.errors or session.criticals: 73 | text += " – " 74 | if session.errors: 75 | text += f"{session.errors} " + ut.f_plural(session.errors, "Error", "Errors") 76 | if session.criticals: 77 | text += ", " 78 | if session.criticals: 79 | text += f"{session.criticals} " + ut.f_plural( 80 | session.criticals, "Critical", "Criticals" 81 | ) 82 | 83 | self.comboBox_sessions.addTextItemLinkedData(text, index) 84 | 85 | def show_log(self) -> None: 86 | """ 87 | Show the log in the text edit. 88 | """ 89 | 90 | session_index = self.comboBox_sessions.currentLinkedData() 91 | 92 | session = self.sessions[session_index] 93 | self.log_viewer.show_log(session.text) 94 | 95 | @staticmethod 96 | def open_issues() -> None: 97 | """ 98 | Open the issues page in the browser. 99 | """ 100 | logger.debug("Opening github issues page.") 101 | Qg.QDesktopServices.openUrl(Qc.QUrl("https://github.com/VoxelCubes/DeepQt/issues")) 102 | 103 | def copy_to_clipboard(self) -> None: 104 | """ 105 | Copy the issue report to the clipboard. 106 | """ 107 | logger.debug("Copying issue report to clipboard.") 108 | text = self.log_viewer.get_log() 109 | Qg.QGuiApplication.clipboard().setText(text) 110 | -------------------------------------------------------------------------------- /deepqt/key_button.py: -------------------------------------------------------------------------------- 1 | import PySide6.QtWidgets as Qw 2 | import PySide6.QtGui as Qg 3 | import PySide6.QtCore as Qc 4 | from PySide6.QtCore import Qt 5 | 6 | import deepqt.constants as ct 7 | import deepqt.driver_api_key_input as dak 8 | 9 | 10 | class APIKeyButton(Qw.QPushButton): 11 | """ 12 | A button to show the API key dialog, while also storing the key. 13 | """ 14 | 15 | key: ct.APIKey | None 16 | help_html: ct.HTML | None 17 | 18 | def __init__(self, parent=None) -> None: 19 | """ 20 | Set up a button to show the API key dialog. 21 | 22 | :param parent: Parent widget. 23 | """ 24 | # Qg.QIcon.fromTheme("database-change-key") 25 | super(APIKeyButton, self).__init__("Set Key", parent) 26 | 27 | self.key = None 28 | self.help_html = None 29 | self.clicked.connect(self.get_input) 30 | 31 | def setup(self, key: ct.APIKey, help_html: ct.HTML = "") -> None: 32 | """ 33 | Set the key and help text. This must be done before the button is clicked. 34 | 35 | :param key: The API key. 36 | :param help_html: [Optional] The help text for the API key. Leave empty for no help. 37 | """ 38 | self.key = key 39 | self.help_html = help_html 40 | 41 | def get_key(self) -> ct.APIKey: 42 | """ 43 | Get the API key. 44 | """ 45 | return self.key 46 | 47 | def set_key(self, key: ct.APIKey) -> None: 48 | """ 49 | Set the API key. 50 | """ 51 | self.key = key 52 | 53 | def get_input(self) -> None: 54 | """ 55 | Show the API key dialog. 56 | """ 57 | dialog = dak.KeyInputDialog(self, self.key, self.help_html) 58 | if dialog.exec(): 59 | self.key = dialog.get_key() 60 | -------------------------------------------------------------------------------- /deepqt/log_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import getpass 3 | from datetime import datetime 4 | from typing import Sequence 5 | 6 | import deepqt.utils as ut 7 | 8 | MAX_SESSIONS = 30 9 | 10 | 11 | def get_username() -> str: 12 | """ 13 | Get the username of the current user. 14 | We want to censor this in the log files for privacy. 15 | 16 | :return: The username of the current user. 17 | """ 18 | return getpass.getuser() 19 | 20 | 21 | def censor(text: str) -> str: 22 | """ 23 | Censor the username from the log file. 24 | We want to censor this in the log files for privacy. 25 | 26 | :param text: The text to censor. 27 | :return: The censored text. 28 | """ 29 | return text.replace(get_username(), "") 30 | 31 | 32 | class LogSession: 33 | text: str 34 | date_time: datetime 35 | errors: int 36 | criticals: int 37 | corrupted: bool 38 | 39 | def __init__(self, text: str) -> None: 40 | self.text = text 41 | self.date_time = datetime.now() 42 | self.errors = 0 43 | self.criticals = 0 44 | self.corrupted = False 45 | self.parse() 46 | self.censor() 47 | 48 | def parse(self) -> None: 49 | self.errors = self.text.count("ERROR") 50 | self.criticals = self.text.count("CRITICAL") 51 | # Find the first parsable date in the log session. 52 | # Example: 2024-01-27 22:51:03.778 53 | first_date = re.search(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}", self.text) 54 | if first_date: 55 | self.date_time = datetime.strptime(first_date.group(), "%Y-%m-%d %H:%M:%S") 56 | else: 57 | # If no date is found, we assume the log session is corrupted, since every 58 | # log session should start with a date. 59 | self.corrupted = True 60 | 61 | def censor(self) -> None: 62 | self.text = censor(self.text) 63 | 64 | 65 | def load_log_file() -> str | None: 66 | """ 67 | Load the log file. If none exists, return None. 68 | None existing is a bit odd, but it is possible that the log file was deleted. 69 | Not a big deal, but it is something to consider. 70 | 71 | :return: The log file contents. 72 | """ 73 | contents = None 74 | try: 75 | with open(ut.get_log_path(), "r", encoding="utf-8") as file: 76 | contents = file.read() 77 | except FileNotFoundError: 78 | pass 79 | return contents 80 | 81 | 82 | def parse_log_file(contents: str, max_sessions: int = MAX_SESSIONS) -> Sequence[LogSession]: 83 | """ 84 | Parse the log file contents. 85 | 86 | :param contents: The log file contents. 87 | :param max_sessions: The maximum number of sessions to parse. 88 | :return: A list of log sessions, sorted by date, newest first. 89 | """ 90 | 91 | sessions = [] 92 | # Split the log file into sections based on start and shutdown messages 93 | raw_sessions = re.split(rf"({ut.STARTUP_MESSAGE})", contents) 94 | 95 | # Pairing each startup message with its corresponding log session 96 | for i in range(len(raw_sessions) - 2, 0, -2): 97 | if raw_sessions[i] == ut.STARTUP_MESSAGE and i + 1 < len(raw_sessions): 98 | session_text = raw_sessions[i + 1] 99 | # If there is a shutdown message, find it and truncate the session text 100 | shutdown_index = session_text.find(ut.SHUTDOWN_MESSAGE, -500) 101 | if shutdown_index != -1: 102 | shutdown_index = session_text.find("\n", shutdown_index) 103 | if shutdown_index != -1: 104 | session_text = session_text[:shutdown_index] 105 | 106 | session_text = session_text.strip() 107 | 108 | # Append sessions in reverse order 109 | sessions.append(LogSession(session_text)) 110 | 111 | # Stop if the maximum number of sessions is reached 112 | if len(sessions) >= max_sessions: 113 | break 114 | 115 | return sessions 116 | -------------------------------------------------------------------------------- /deepqt/memory_watcher.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | from PySide6.QtCore import Signal, QObject, QTimer 3 | 4 | # Attempt to import pynvml for GPU monitoring 5 | try: 6 | import pynvml 7 | 8 | _pynvml_available = True 9 | except ImportError: 10 | _pynvml_available = False 11 | 12 | 13 | class MemoryWatcher(QObject): 14 | # Monitor system memory usage to warn of impending OOM errors. 15 | oom_warning = Signal(str) 16 | oom_relaxed = Signal() 17 | 18 | def __init__(self, parent=None): 19 | super().__init__(parent) 20 | self.timer = QTimer(self) 21 | self.timer.timeout.connect(self.check_memory) 22 | self.gpu_available = False 23 | 24 | if _pynvml_available: 25 | try: 26 | pynvml.nvmlInit() 27 | device_count = pynvml.nvmlDeviceGetCount() 28 | if device_count > 0: 29 | self.gpu_available = True 30 | except pynvml.NVMLError: 31 | self.gpu_available = False 32 | else: 33 | self.gpu_available = False 34 | 35 | def start(self): 36 | # Start the timer to check memory every second (2000 ms) 37 | self.timer.start(2000) 38 | 39 | def stop(self): 40 | # Stop the timer 41 | self.timer.stop() 42 | 43 | def check_memory(self): 44 | # Monitor system memory (RAM) 45 | mem = psutil.virtual_memory() 46 | swap = psutil.swap_memory() 47 | oom: bool = False 48 | 49 | # Check conditions for RAM and swap. 50 | if swap.total == 0: # No swap available. 51 | if mem.percent >= 80: 52 | self.oom_warning.emit( 53 | self.tr("RAM usage has reached {mem}%").format(mem=round(mem.percent)) 54 | ) 55 | oom = True 56 | else: # Swap is available 57 | if mem.percent >= 90: 58 | self.oom_warning.emit( 59 | self.tr("RAM usage has reached {mem}%").format(mem=round(mem.percent)) 60 | ) 61 | oom = True 62 | 63 | # Monitor VRAM if GPUs are available 64 | if self.gpu_available: 65 | try: 66 | device_count = pynvml.nvmlDeviceGetCount() 67 | for i in range(device_count): 68 | handle = pynvml.nvmlDeviceGetHandleByIndex(i) 69 | meminfo = pynvml.nvmlDeviceGetMemoryInfo(handle) 70 | vram_total = meminfo.total 71 | vram_used = meminfo.used 72 | vram_percent = (vram_used / vram_total) * 100 73 | if vram_percent >= 80: 74 | self.oom_warning.emit( 75 | "GPU {gpu}: VRAM usage has reached {vmem}%".format( 76 | gpu=i, vmem=round(vram_percent) 77 | ) 78 | ) 79 | oom = True 80 | except pynvml.NVMLError as err: 81 | # Handle NVML errors 82 | print(f"Error querying GPU memory: {err}") 83 | 84 | if not oom: 85 | self.oom_relaxed.emit() 86 | 87 | def __del__(self): 88 | if self.gpu_available and _pynvml_available: 89 | try: 90 | pynvml.nvmlShutdown() 91 | except pynvml.NVMLError: 92 | pass 93 | -------------------------------------------------------------------------------- /deepqt/quote_protection.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | """ 3 | Replaces/restores matching quotes to make them deepl-safe. 4 | Usage: quote-saver --restore 5 | """ 6 | import argparse 7 | import re 8 | 9 | 10 | # ============================================== Protection ============================================== 11 | p_direct_speech_j_re = re.compile(r"^「(.*)」$") 12 | p_direct_speech_e_re = re.compile(r"^“(.*)”$") 13 | p_direct_speech = (p_direct_speech_j_re, p_direct_speech_e_re, r"[QUOTE]\n\1\n[ENDQUOTE]") 14 | 15 | p_thought_speech_j_re = re.compile(r"^『(.*)』$") 16 | p_thought_speech_e_re = re.compile(r"^‘(.*)’$") 17 | p_thought_speech = (p_thought_speech_j_re, p_thought_speech_e_re, r"[TELNET]\n\1\n[ENDTELNET]") 18 | 19 | p_thought_internal_j_re = re.compile(r"^((.*))$") 20 | p_thought_internal_e_re = re.compile(r"^\((.*)\)$") 21 | p_thought_internal = ( 22 | p_thought_internal_j_re, 23 | p_thought_internal_e_re, 24 | r"[THOUGHT]\n\1\n[ENDTHOUGHT]", 25 | ) 26 | 27 | p_raphael_j_re = re.compile(r"^《(.*)》$") 28 | p_raphael_e_re = re.compile(r"^<<(.*)>>$") 29 | p_raphael = (p_raphael_j_re, p_raphael_e_re, r"[RAPHAEL]\n\1\n[ENDRAPHAEL]") 30 | 31 | # ============================================== Restoration ============================================== 32 | r_direct_speech_re = re.compile(r"\[QUOTE\].*?\n+(.*?)\n+?\[ENDQUOTE\]") 33 | r_direct_speech = (r_direct_speech_re, r"“\1”") 34 | 35 | r_thought_speech_re = re.compile(r"\[TELNET\].*?\n+(.*?)\n+?\[ENDTELNET\]") 36 | r_thought_speech = (r_thought_speech_re, r"<\1>") 37 | 38 | r_thought_internal_re = re.compile(r"\[THOUGHT\].*?\n+(.*?)\n+?\[ENDTHOUGHT\]") 39 | r_thought_internal = (r_thought_internal_re, r"(\1)") 40 | 41 | r_raphael_re = re.compile(r"\[RAPHAEL\].*?\n+(.*?)\n+?\[ENDRAPHAEL\]") 42 | r_raphael = (r_raphael_re, r"<<\1>>") 43 | 44 | 45 | def protect_line(line: str) -> None: 46 | """ 47 | Apply each protection pattern to a line. 48 | :param line: 49 | :return: 50 | """ 51 | for p_pattern in (p_direct_speech, p_thought_speech, p_thought_internal, p_raphael): 52 | j_re, e_re, repl = p_pattern 53 | line = j_re.sub(repl, line) 54 | line = e_re.sub(repl, line) 55 | 56 | return line 57 | 58 | 59 | def protect_text(text: str) -> None: 60 | """ 61 | Apply each protection pattern to the full text. 62 | :param text: 63 | :return: 64 | """ 65 | return "\n".join(protect_line(line) for line in text.splitlines()) 66 | 67 | 68 | def restore(text_: str) -> None: 69 | """ 70 | Apply each restoration pattern to the full text due to the line breaks introduced by the protection patterns. 71 | :param text_: 72 | :return: 73 | """ 74 | for r_pattern in (r_direct_speech, r_thought_speech, r_thought_internal, r_raphael): 75 | r_re, repl = r_pattern 76 | text_ = r_re.sub(repl, text_) 77 | return text_ 78 | 79 | 80 | if __name__ == "__main__": 81 | # Parsing arguments 82 | parser = argparse.ArgumentParser( 83 | description="Replaces/restores matching quotes to make them deepl-safe." 84 | ) 85 | parser.add_argument("inputtext", type=str, help="Input text file.") 86 | parser.add_argument("outputtext", type=str, help="Output text file.") 87 | parser.add_argument("--restore", action="store_true", help="Restore the text.") 88 | args = parser.parse_args() 89 | 90 | # Restoration is handled for the whole file at once. 91 | if args.restore: 92 | with open(args.inputtext, "r", encoding="utf-8") as f: 93 | text = f.read() 94 | text = restore(text) 95 | with open(args.outputtext, "w", encoding="utf-8") as f: 96 | f.write(text) 97 | 98 | # Protection is done line by line. 99 | else: 100 | with open(args.inputtext, "r", encoding="utf-8") as f: 101 | lines = f.readlines() 102 | lines = [protect_line(line) for line in lines] 103 | with open(args.outputtext, "w", encoding="utf-8") as f: 104 | f.writelines(lines) 105 | -------------------------------------------------------------------------------- /deepqt/trie.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | class Trie: 5 | """Regex::Trie in Python. Creates a Trie out of a list of words. The trie can be exported to a Regex pattern. 6 | The corresponding Regex should match much faster than a simple Regex union.""" 7 | 8 | def __init__(self) -> None: 9 | self.data = {} 10 | 11 | def add(self, word) -> None: 12 | ref = self.data 13 | for char in word: 14 | ref[char] = char in ref and ref[char] or {} 15 | ref = ref[char] 16 | ref[""] = 1 17 | 18 | def dump(self) -> None: 19 | return self.data 20 | 21 | @staticmethod 22 | def quote(char) -> None: 23 | return re.escape(char) 24 | 25 | def _pattern(self, pData) -> None: 26 | data = pData 27 | if "" in data and len(data.keys()) == 1: 28 | return None 29 | 30 | alt = [] 31 | cc = [] 32 | q = 0 33 | for char in sorted(data.keys()): 34 | if isinstance(data[char], dict): 35 | try: 36 | recurse = self._pattern(data[char]) 37 | alt.append(self.quote(char) + recurse) 38 | except Exception: 39 | cc.append(self.quote(char)) 40 | else: 41 | q = 1 42 | cconly = not len(alt) > 0 43 | 44 | if len(cc) > 0: 45 | if len(cc) == 1: 46 | alt.append(cc[0]) 47 | else: 48 | alt.append("[" + "".join(cc) + "]") 49 | 50 | if len(alt) == 1: 51 | result = alt[0] 52 | else: 53 | result = "(?:" + "|".join(alt) + ")" 54 | 55 | if q: 56 | if cconly: 57 | result += "?" 58 | else: 59 | result = "(?:%s)?" % result 60 | return result 61 | 62 | def pattern(self) -> None: 63 | return self._pattern(self.dump()) 64 | 65 | 66 | def trie_regex_from_words(words, prefix="", suffix="") -> None: 67 | trie = Trie() 68 | for word in words: 69 | trie.add(word) 70 | # Is there a case for case insensitivity? 71 | return re.compile(prefix + trie.pattern() + suffix) # , re.IGNORECASE) 72 | -------------------------------------------------------------------------------- /deepqt/ui_generated_files/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/deepqt/ui_generated_files/__init__.py -------------------------------------------------------------------------------- /deepqt/ui_generated_files/ui_api_key_deepl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'api_key_deepl.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.7.3 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog, 19 | QDialogButtonBox, QFormLayout, QLabel, QLineEdit, 20 | QSizePolicy, QVBoxLayout, QWidget) 21 | 22 | class Ui_Dialog_API(object): 23 | def setupUi(self, Dialog_API): 24 | if not Dialog_API.objectName(): 25 | Dialog_API.setObjectName(u"Dialog_API") 26 | Dialog_API.resize(450, 200) 27 | self.verticalLayout = QVBoxLayout(Dialog_API) 28 | self.verticalLayout.setObjectName(u"verticalLayout") 29 | self.formLayout = QFormLayout() 30 | self.formLayout.setObjectName(u"formLayout") 31 | self.label = QLabel(Dialog_API) 32 | self.label.setObjectName(u"label") 33 | 34 | self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label) 35 | 36 | self.lineEdit_api_key = QLineEdit(Dialog_API) 37 | self.lineEdit_api_key.setObjectName(u"lineEdit_api_key") 38 | self.lineEdit_api_key.setMaxLength(100) 39 | 40 | self.formLayout.setWidget(0, QFormLayout.FieldRole, self.lineEdit_api_key) 41 | 42 | self.label_2 = QLabel(Dialog_API) 43 | self.label_2.setObjectName(u"label_2") 44 | 45 | self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2) 46 | 47 | self.comboBox_api_type = QComboBox(Dialog_API) 48 | self.comboBox_api_type.addItem("") 49 | self.comboBox_api_type.addItem("") 50 | self.comboBox_api_type.setObjectName(u"comboBox_api_type") 51 | 52 | self.formLayout.setWidget(1, QFormLayout.FieldRole, self.comboBox_api_type) 53 | 54 | 55 | self.verticalLayout.addLayout(self.formLayout) 56 | 57 | self.buttonBox = QDialogButtonBox(Dialog_API) 58 | self.buttonBox.setObjectName(u"buttonBox") 59 | self.buttonBox.setOrientation(Qt.Horizontal) 60 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Help|QDialogButtonBox.Ok) 61 | 62 | self.verticalLayout.addWidget(self.buttonBox) 63 | 64 | 65 | self.retranslateUi(Dialog_API) 66 | self.buttonBox.accepted.connect(Dialog_API.accept) 67 | self.buttonBox.rejected.connect(Dialog_API.reject) 68 | 69 | QMetaObject.connectSlotsByName(Dialog_API) 70 | # setupUi 71 | 72 | def retranslateUi(self, Dialog_API): 73 | Dialog_API.setWindowTitle(QCoreApplication.translate("Dialog_API", u"Account Settings", None)) 74 | self.label.setText(QCoreApplication.translate("Dialog_API", u"API Key:", None)) 75 | self.label_2.setText(QCoreApplication.translate("Dialog_API", u"Account Type:", None)) 76 | self.comboBox_api_type.setItemText(0, QCoreApplication.translate("Dialog_API", u"DeepL API FREE", None)) 77 | self.comboBox_api_type.setItemText(1, QCoreApplication.translate("Dialog_API", u"DeepL API PRO", None)) 78 | 79 | # retranslateUi 80 | 81 | -------------------------------------------------------------------------------- /deepqt/ui_generated_files/ui_api_key_input.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'api_key_input.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.7.3 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox, 19 | QFormLayout, QLabel, QLineEdit, QSizePolicy, 20 | QVBoxLayout, QWidget) 21 | 22 | class Ui_Dialog_API(object): 23 | def setupUi(self, Dialog_API): 24 | if not Dialog_API.objectName(): 25 | Dialog_API.setObjectName(u"Dialog_API") 26 | Dialog_API.resize(450, 141) 27 | self.verticalLayout = QVBoxLayout(Dialog_API) 28 | self.verticalLayout.setObjectName(u"verticalLayout") 29 | self.formLayout = QFormLayout() 30 | self.formLayout.setObjectName(u"formLayout") 31 | self.label = QLabel(Dialog_API) 32 | self.label.setObjectName(u"label") 33 | 34 | self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label) 35 | 36 | self.lineEdit_api_key = QLineEdit(Dialog_API) 37 | self.lineEdit_api_key.setObjectName(u"lineEdit_api_key") 38 | self.lineEdit_api_key.setMaxLength(100) 39 | 40 | self.formLayout.setWidget(0, QFormLayout.FieldRole, self.lineEdit_api_key) 41 | 42 | 43 | self.verticalLayout.addLayout(self.formLayout) 44 | 45 | self.buttonBox = QDialogButtonBox(Dialog_API) 46 | self.buttonBox.setObjectName(u"buttonBox") 47 | self.buttonBox.setOrientation(Qt.Horizontal) 48 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Help|QDialogButtonBox.Ok) 49 | 50 | self.verticalLayout.addWidget(self.buttonBox) 51 | 52 | 53 | self.retranslateUi(Dialog_API) 54 | self.buttonBox.accepted.connect(Dialog_API.accept) 55 | self.buttonBox.rejected.connect(Dialog_API.reject) 56 | 57 | QMetaObject.connectSlotsByName(Dialog_API) 58 | # setupUi 59 | 60 | def retranslateUi(self, Dialog_API): 61 | Dialog_API.setWindowTitle(QCoreApplication.translate("Dialog_API", u"API Key", None)) 62 | self.label.setText(QCoreApplication.translate("Dialog_API", u"API Key:", None)) 63 | # retranslateUi 64 | 65 | -------------------------------------------------------------------------------- /deepqt/ui_generated_files/ui_text_preview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'text_preview.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.7.3 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QDialog, QPushButton, QSizePolicy, 19 | QTabWidget, QVBoxLayout, QWidget) 20 | 21 | class Ui_TextPreview(object): 22 | def setupUi(self, TextPreview): 23 | if not TextPreview.objectName(): 24 | TextPreview.setObjectName(u"TextPreview") 25 | TextPreview.resize(720, 800) 26 | self.verticalLayout = QVBoxLayout(TextPreview) 27 | self.verticalLayout.setObjectName(u"verticalLayout") 28 | self.verticalLayout.setContentsMargins(0, 0, 0, -1) 29 | self.tabWidget = QTabWidget(TextPreview) 30 | self.tabWidget.setObjectName(u"tabWidget") 31 | self.tabWidget.setUsesScrollButtons(False) 32 | 33 | self.verticalLayout.addWidget(self.tabWidget) 34 | 35 | self.pushButton_save = QPushButton(TextPreview) 36 | self.pushButton_save.setObjectName(u"pushButton_save") 37 | sizePolicy = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) 38 | sizePolicy.setHorizontalStretch(0) 39 | sizePolicy.setVerticalStretch(0) 40 | sizePolicy.setHeightForWidth(self.pushButton_save.sizePolicy().hasHeightForWidth()) 41 | self.pushButton_save.setSizePolicy(sizePolicy) 42 | icon = QIcon() 43 | iconThemeName = u"document-save" 44 | if QIcon.hasThemeIcon(iconThemeName): 45 | icon = QIcon.fromTheme(iconThemeName) 46 | else: 47 | icon.addFile(u".", QSize(), QIcon.Mode.Normal, QIcon.State.Off) 48 | 49 | self.pushButton_save.setIcon(icon) 50 | 51 | self.verticalLayout.addWidget(self.pushButton_save, 0, Qt.AlignHCenter) 52 | 53 | 54 | self.retranslateUi(TextPreview) 55 | 56 | QMetaObject.connectSlotsByName(TextPreview) 57 | # setupUi 58 | 59 | def retranslateUi(self, TextPreview): 60 | TextPreview.setWindowTitle(QCoreApplication.translate("TextPreview", u"Placeholder", None)) 61 | self.pushButton_save.setText(QCoreApplication.translate("TextPreview", u"Save Preview to File", None)) 62 | # retranslateUi 63 | 64 | -------------------------------------------------------------------------------- /docs/api_help.md: -------------------------------------------------------------------------------- 1 | # API help 2 | 3 | This tool is a front-end for the DeepL API, the world's best AI translator. 4 | That necessitates access to the API using an API key. 5 | You can sign up for a free or paid account at [DeepL.com](https://www.deepl.com/pro-api). 6 | 7 | Once you have an account, visit your [account page](https://www.deepl.com/account/summary) to get your API key. 8 | 9 | ![api key on account page](../media/api_key.png) 10 | 11 | 12 | Copy the API key and open the account settings in DeepQt. 13 | Paste the key into the text field meant for it, then select the account type you signed up for. 14 | 15 | ![account settings in deepqt](../media/account_config.png) 16 | 17 | 18 | If you have a free account or set a limit on your pro account, translation may fail once you exceed your monthly quota. 19 | You can see your current usage level in DeepQt. -------------------------------------------------------------------------------- /docs/glossary_help.md: -------------------------------------------------------------------------------- 1 | # Glossary Format 2 | 3 | These glossaries do not take advantage of DeepL's integrated glossaries, however, they are more powerful in other ways. 4 | You can preview the changes the glossary made and even save that preview to a file. This way, you can also use DeepQt 5 | as a powerful replacement engine for text files and epubs alike. 6 | 7 | Check out some handy [templates to get you started](https://github.com/VoxelCubes/DeepQt/tree/master/Glossary%20Templates). 8 | 9 | To see what the glossary does to your text, click the "Preview" button. This will show you the text with the 10 | glossary applied. 11 | And if you like what you see, you can save the preview to a file. 12 | This works for plain text and whole epub files alike. 13 | 14 | A glossary is a collection of terms, each of which consists of a pattern to look for in the left cell, and the pattern to substitute it with in the right cell. The substitution patterns require a prefix symbol which dictates what type of term it is. DeepQt glossaries support 6 different types of terms, processed in the following order: 15 | 16 | #### Exact match 17 | 18 | | pattern | #substitution | 19 | |:-------:| ------------- | 20 | | painter | #Maler | 21 | 22 | The simplest form of substitution. It simply replaces the pattern with the substitution regardless of context. 23 | 24 | **Note:** This pattern type inserts a trailing space. This can be useful when a word was replaced in the middle of another word and in conjunction with title suffix and prefix options. See [No Suffix](#NoSuffix) terms to avoid this behavior. 25 | 26 | #### Regex 27 | 28 | | pattern | :subsitution | 29 | | ------- | ------------ | 30 | | / +/ | /: / | 31 | 32 | When you need full control over context, [Regular Expressions](https://en.wikipedia.org/wiki/Regular_expression) are the way to go. Here to condense multiple spaces into only one. The use of capture groups can also allow you to change the order of words. 33 | 34 | **Note:** To use capture groups, you need to use Python-style syntax. That means group 1 is \1, instead of $1, as you may see in other styles. Further, if your patterns include whitespace in the front or back, your editor is likely to strip it out. To prevent this, you can wrap your patterns in slashes, as seen in the example (This works for all pattern types). 35 | 36 | #### Title suffixes (honorifics) 37 | 38 | | pattern | $substitution | 39 | | -------- | ------------- | 40 | | of Power | $de force | 41 | 42 | These substitutions only engage when preceded by the pattern `[a-z] ` (any lower-case letter, followed by a space). This way they aren't accidentally inserted in improper locations. Particularly useful when dealing with languages that have suffix honorific systems. 43 | 44 | #### Title prefixes 45 | 46 | | pattern | !substitution | 47 | | ------- | ------------- | 48 | | Master | !Herr | 49 | 50 | Similar to title suffixes. These substitutions only engage when followed by the pattern  [A-Z] (a space, followed by any upper-case letter). 51 | 52 | #### No Suffix 53 | 54 | | pattern | |substitution | 55 | | -------- | ----------- | 56 | | Künstler | |artist | 57 | 58 | Like exact patterns, but do not insert a trailing space. 59 | 60 | #### Post term 61 | 62 | | pattern | ~substitution | 63 | | ------- | ------------- | 64 | | paper | ~Arbeit | 65 | 66 | Like exact matches, but are simply performed last. This can be useful to avoid potential collisions with other terms that should be processed first. 67 | 68 | ## Supported File Formats 69 | 70 | DeepQt uses [pyexcel](http://docs.pyexcel.org/en/latest/) to read glossaries. It supports a plethora of formats, including: 71 | .ods, .xlsx, .html, .csv, .csvz, .tsv, .tsvz, .rst, .json ... and many more. 72 | You can see the full list [here](https://github.com/pyexcel/pyexcel#feature-highlights). -------------------------------------------------------------------------------- /flatpak/io.github.voxelcubes.deepqt.appdata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.github.voxelcubes.deepqt 4 | io.github.voxelcubes.deepqt.desktop 5 | DeepQt 6 | Harness the power of the DeepL API 7 | CC0-1.0 8 | GPL-3.0-or-later 9 | https://github.com/VoxelCubes/DeepQt 10 | 11 |

DeepQt is a user-friendly application that harnesses the power of the DeepL API for translation purposes. 12 | With a clean interface and support for batch processing of plain text files and EPUB books, 13 | DeepQt makes it easy to translate texts efficiently and effectively.

14 | 15 |

DeepQt also features higher-level glossaries allowing you to make replacements before translation, 16 | to ensure consistnecy in terms. These can even be applied without translating anything!

17 | 18 |

To use DeepQt's translation features, you will need a DeepL API license, either free or pro.

19 |
20 | 21 | 22 | 23 | https://raw.githubusercontent.com/VoxelCubes/DeepQt/master/media/example_screenshot.png 24 | 25 | 26 | 27 | 28 | 29 | 30 | VoxelCubes 31 | 32 | Utility 33 | Translation 34 | 35 | 36 | deepqt 37 | 38 |
39 | -------------------------------------------------------------------------------- /icons/copy_from_dark_to_light.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | 5 | 6 | def simple_replace_color_in_svg(input_folder, output_folder, old_color, new_color) -> None: 7 | # Ensure the output folder exists 8 | if not os.path.exists(output_folder): 9 | os.makedirs(output_folder) 10 | 11 | # Loop through all the SVG files in the input folder 12 | for svg_file in os.listdir(input_folder): 13 | if svg_file.endswith(".svg"): 14 | # Construct the full path to the SVG file 15 | input_path = os.path.join(input_folder, svg_file) 16 | 17 | # Read the SVG file as a text 18 | with open(input_path, "r") as f: 19 | file_content = f.read() 20 | 21 | # Replace the color 22 | modified_content = file_content.replace(old_color, new_color) 23 | 24 | # Construct the full path to the output SVG file 25 | output_path = os.path.join(output_folder, svg_file) 26 | 27 | # Write the modified SVG back to a new file 28 | with open(output_path, "w") as f: 29 | f.write(modified_content) 30 | 31 | 32 | # Define parameters 33 | input_folder = "deepqt/data/custom_icons/dark" 34 | output_folder = "deepqt/data/custom_icons/light" 35 | old_color = "#fcfcfc" 36 | new_color = "#232629" 37 | 38 | # Run the function 39 | simple_replace_color_in_svg(input_folder, output_folder, old_color, new_color) 40 | -------------------------------------------------------------------------------- /icons/theme_list.yaml: -------------------------------------------------------------------------------- 1 | Theme directories: 2 | - /usr/share/icons/breeze 3 | - /usr/share/icons/breeze-dark 4 | 5 | Files: 6 | actions: 7 | 16: 8 | - application-menu 9 | - arrow-down 10 | - arrow-up 11 | - configure 12 | - dialog-cancel 13 | - dialog-ok 14 | - dialog-ok-apply 15 | - document-open 16 | - document-open-folder 17 | - document-preview 18 | - document-save 19 | - document-multiple 20 | - download 21 | - edit-clear 22 | - edit-copy 23 | - edit-delete 24 | - edit-delete-remove 25 | - games-config-theme 26 | - help-contents 27 | - help-hint 28 | - internet-services 29 | - list-add 30 | - media-playback-start 31 | - process-stop 32 | - tools-report-bug 33 | - view-refresh 34 | - view-list-text 35 | - window-close 36 | mimetypes: 37 | 16: 38 | - text-x-changelog 39 | 32: 40 | - application-epub+zip 41 | - text-x-generic 42 | status: 43 | 16: 44 | - data-error 45 | - data-warning 46 | - state-error 47 | - state-offline 48 | - state-ok 49 | - image-missing 50 | - dialog-warning 51 | 22: 52 | - dialog-error 53 | -------------------------------------------------------------------------------- /media/Web card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/Web card.png -------------------------------------------------------------------------------- /media/Web card.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/Web card.xcf -------------------------------------------------------------------------------- /media/account_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/account_config.png -------------------------------------------------------------------------------- /media/api_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/api_key.png -------------------------------------------------------------------------------- /media/deepqt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/deepqt.png -------------------------------------------------------------------------------- /media/example_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/example_screenshot.png -------------------------------------------------------------------------------- /media/example_screenshot_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/media/example_screenshot_windows.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools", "wheel"] 4 | 5 | [tool.black] 6 | line-length = 100 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs 2 | cattrs 3 | deepl 4 | loguru 5 | PySide6 6 | xdg 7 | pyexcel 8 | pyexcel-odsr 9 | pyexcel-xls 10 | pyexcel-xlsx 11 | pyexcel-htmlr 12 | 13 | beautifulsoup4 14 | minify_html 15 | lxml 16 | setuptools 17 | PyYAML 18 | psutil 19 | chardet -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = deepqt 3 | version = 1.1.7 4 | description = Harness the power of the DeepL API with this friendly user interface. 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown; charset=UTF-8; variant=GFM 7 | url = https://github.com/VoxelCubes/DeepQt 8 | keywords = deepl, deepl api, qt, pyside6 9 | license_files = LICENSE 10 | classifiers = 11 | Programming Language :: Python :: 3.10 12 | License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) 13 | Operating System :: POSIX :: Linux 14 | Operating System :: Microsoft :: Windows 15 | 16 | [options] 17 | install_requires = 18 | attrs 19 | cattrs 20 | deepl 21 | loguru 22 | PySide6 23 | xdg 24 | psutil 25 | pyexcel 26 | pyexcel-odsr 27 | pyexcel-xls 28 | pyexcel-xlsx 29 | pyexcel-htmlr 30 | pyyaml 31 | beautifulsoup4 32 | minify_html 33 | lxml 34 | chardet 35 | python_requires = >=3.10 36 | packages=find: 37 | 38 | [options.entry_points] 39 | console_scripts = 40 | deepqt = deepqt.main:main 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/__init__.py -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from importlib import resources 3 | from tests import mock_files 4 | 5 | 6 | def read_mock_file(file_name: str, module=mock_files) -> str: 7 | with resources.files(module) as mock_path: 8 | path = mock_path / file_name 9 | file_contents = path.read_text() 10 | return file_contents 11 | 12 | 13 | def mock_file_path(file_name: str, module=mock_files) -> Path: 14 | with resources.files(module) as mock_path: 15 | path = mock_path / file_name 16 | return path 17 | -------------------------------------------------------------------------------- /tests/mock_files/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/__init__.py -------------------------------------------------------------------------------- /tests/mock_files/blank_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/blank_file -------------------------------------------------------------------------------- /tests/mock_files/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/config/__init__.py -------------------------------------------------------------------------------- /tests/mock_files/config/coercible_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang_from": "Valid language code", 3 | "lang_to": "EN-US", 4 | "use_fixed_output_path": 0, 5 | "fixed_output_path": "", 6 | "glossary_path": "", 7 | "use_glossary": "yes", 8 | "dump_on_abort": false, 9 | "epub_nuke_kobo": true, 10 | "epub_nuke_ruby": true, 11 | "epub_nuke_indents": true, 12 | "epub_crush": false, 13 | "epub_make_text_horizontal": true, 14 | "epub_ignore_empty_html": true, 15 | "last_backend": "1", 16 | "backend_configs": { 17 | "0": { 18 | "backend_type": "MOCK", 19 | "avg_time_per_mille": "-2.0", 20 | "chunk_size": 1000.0, 21 | "wait_time": 1000 22 | }, 23 | "1": { 24 | "backend_type": "DEEPL", 25 | "avg_time_per_mille": -1.0, 26 | "api_key": "sdkfjhasdflasjfhjsalhfla", 27 | "tl_max_chunks": 20, 28 | "tl_min_chunk_size": 5000, 29 | "tl_preserve_formatting": 42, 30 | "wait_time": 1000 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tests/mock_files/config/empty.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/mock_files/config/good.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang_from": "Valid language code", 3 | "lang_to": "EN-US", 4 | "use_fixed_output_path": false, 5 | "fixed_output_path": "", 6 | "glossary_path": "", 7 | "use_glossary": true, 8 | "dump_on_abort": false, 9 | "epub_nuke_kobo": true, 10 | "epub_nuke_ruby": true, 11 | "epub_nuke_indents": true, 12 | "epub_crush": false, 13 | "epub_make_text_horizontal": true, 14 | "epub_ignore_empty_html": true, 15 | "last_backend": "1", 16 | "backend_configs": { 17 | "0": { 18 | "backend_type": "MOCK", 19 | "avg_time_per_mille": -9000, 20 | "chunk_size": 1000, 21 | "wait_time": 1000 22 | }, 23 | "1": { 24 | "backend_type": "DEEPL", 25 | "avg_time_per_mille": -1.0, 26 | "api_key": "sdkfjhasdflasjfhjsalhfla", 27 | "tl_max_chunks": 20, 28 | "tl_min_chunk_size": 5000, 29 | "tl_preserve_formatting": true, 30 | "wait_time": 1000 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tests/mock_files/config/invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | 1: 01010, 3 | "silly": nonsense 4 | } -------------------------------------------------------------------------------- /tests/mock_files/config/missing_and_extra_keys.json: -------------------------------------------------------------------------------- 1 | { 2 | "nonsense": "This shouldn't be here", 3 | "lang_from": "Valid language code", 4 | "lang_to": "EN-US", 5 | "backend_configs": { 6 | "0": { 7 | "backend_type": "MOCK", 8 | "help": "This should never be here", 9 | "avg_time_per_mille": -1.0, 10 | "chunk_size": 1000, 11 | "more nonsense": 42 12 | }, 13 | "1": { 14 | "backend_type": "DEEPL", 15 | "avg_time_per_mille": -1.0, 16 | "tl_preserve_formatting": true, 17 | "wait_time": 1000 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/mock_files/config/missing_backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang_from": "Valid language code", 3 | "lang_to": "EN-US", 4 | "use_fixed_output_path": false, 5 | "fixed_output_path": "", 6 | "glossary_path": "", 7 | "use_glossary": true, 8 | "dump_on_abort": false, 9 | "epub_nuke_kobo": true, 10 | "epub_nuke_ruby": true, 11 | "epub_nuke_indents": true, 12 | "epub_crush": false, 13 | "epub_make_text_horizontal": true, 14 | "epub_ignore_empty_html": true, 15 | "last_backend": "0", 16 | "backend_configs": { 17 | } 18 | } -------------------------------------------------------------------------------- /tests/mock_files/config/type_mismatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang_from": "Valid language code", 3 | "lang_to": "EN-US", 4 | "use_fixed_output_path": false, 5 | "fixed_output_path": "", 6 | "glossary_path": "", 7 | "use_glossary": true, 8 | "dump_on_abort": false, 9 | "epub_nuke_kobo": true, 10 | "epub_nuke_ruby": true, 11 | "epub_nuke_indents": true, 12 | "epub_crush": false, 13 | "epub_make_text_horizontal": true, 14 | "epub_ignore_empty_html": true, 15 | "last_backend": "0", 16 | "backend_configs": { 17 | "0": { 18 | "backend_type": "MOCK", 19 | "avg_time_per_mille": "nonsense", 20 | "chunk_size": 1000, 21 | "wait_time": 1000 22 | }, 23 | "1": { 24 | "backend_type": "DEEPL", 25 | "avg_time_per_mille": -1.0, 26 | "api_key": "sdkfjhasdflasjfhjsalhfla", 27 | "tl_max_chunks": null, 28 | "tl_min_chunk_size": 5000, 29 | "tl_preserve_formatting": true, 30 | "wait_time": 1000 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tests/mock_files/config/unknown_backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang_from": "DE-DE", 3 | "lang_to": "EN-US", 4 | "use_fixed_output_path": false, 5 | "fixed_output_path": "", 6 | "glossary_path": "", 7 | "use_glossary": true, 8 | "dump_on_abort": false, 9 | "epub_nuke_kobo": true, 10 | "epub_nuke_ruby": true, 11 | "epub_nuke_indents": true, 12 | "epub_crush": false, 13 | "epub_make_text_horizontal": true, 14 | "epub_ignore_empty_html": true, 15 | "last_backend": "1", 16 | "backend_configs": { 17 | "Wrong backend type": { 18 | "backend_type": "The Unknown???", 19 | "avg_time_per_mille": -1.0, 20 | "chunk_size": 1000, 21 | "wait_time": 1000 22 | }, 23 | "Missing backend type": { 24 | "avg_time_per_mille": -1.0, 25 | "chunk_size": 1000, 26 | "wait_time": 1000 27 | }, 28 | "1": { 29 | "backend_type": "DEEPL", 30 | "avg_time_per_mille": -1.0, 31 | "api_key": "sdkfjhasdflasjfhjsalhfla", 32 | "tl_max_chunks": 20, 33 | "tl_min_chunk_size": 5000, 34 | "tl_preserve_formatting": true, 35 | "wait_time": 42 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tests/mock_files/logs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/logs/__init__.py -------------------------------------------------------------------------------- /tests/mock_files/mime_types/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/__init__.py -------------------------------------------------------------------------------- /tests/mock_files/mime_types/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/archive.zip -------------------------------------------------------------------------------- /tests/mock_files/mime_types/book.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/book.epub -------------------------------------------------------------------------------- /tests/mock_files/mime_types/calc.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/calc.ods -------------------------------------------------------------------------------- /tests/mock_files/mime_types/document.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/document.pdf -------------------------------------------------------------------------------- /tests/mock_files/mime_types/excel.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/excel.xlsx -------------------------------------------------------------------------------- /tests/mock_files/mime_types/impress.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/impress.odp -------------------------------------------------------------------------------- /tests/mock_files/mime_types/powerpoint.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/powerpoint.pptx -------------------------------------------------------------------------------- /tests/mock_files/mime_types/text.txt: -------------------------------------------------------------------------------- 1 | Sample text 2 | -------------------------------------------------------------------------------- /tests/mock_files/mime_types/translation.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | The default Header Comment. 8 | Der Standard-Header-Kommentar. 9 | 10 | 11 | The "Generator" Meta Tag. 12 | Der "Generator"-Meta-Tag. 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/mock_files/mime_types/website.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sample Text 8 | 9 | 10 | 11 | 12 |

Hi Mom!

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/mock_files/mime_types/word.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/word.docx -------------------------------------------------------------------------------- /tests/mock_files/mime_types/writer.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/mime_types/writer.odt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/__init__.py -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/generate.py: -------------------------------------------------------------------------------- 1 | # Import necessary modules 2 | import codecs 3 | 4 | 5 | # Define messages tailored for each encoding 6 | messages = { 7 | "utf-8": "Hello, 世界! Привет, мир! مرحبا بالعالم!", 8 | "utf-16": "Hello, 世界! Привет, мир! مرحبا بالعالم!", 9 | "iso-8859-1": "Hello, world! Bonjour, le monde! ¡Hola, mundo!", 10 | "ascii": "Hello, world! Greetings, Earth!", 11 | "shift_jis": "こんにちは、世界!これはShift JISエンコーディングのテキストサンプルです。日本語の文字が正しく表示されることを確認してください。", 12 | "gb2312": "你好,世界! Hello, 世界!", 13 | "euc_kr": "안녕하세요, 세계! Hello, world!", 14 | "windows-1251": "Hello, мир! Привет, world!", 15 | "iso-8859-5": "Привет, мир! Hello, world!", 16 | "big5": "你好,世界! Hello, 世界!", 17 | } 18 | 19 | # Dictionary to map encoding names to file suffixes 20 | suffixes = { 21 | "utf-8": "utf8", 22 | "utf-16": "utf16", 23 | "iso-8859-1": "iso8859-1", 24 | "ascii": "ascii", 25 | "shift_jis": "shiftjis", 26 | "gb2312": "gb2312", 27 | "euc_kr": "euckr", 28 | "windows-1251": "windows1251", 29 | "iso-8859-5": "iso8859-5", 30 | "big5": "big5", 31 | } 32 | 33 | # Write the message to a file in each encoding 34 | for encoding, message in messages.items(): 35 | # Create a file name based on the encoding 36 | file_name = f"message_{suffixes[encoding]}.txt" 37 | 38 | # Open the file with the specified encoding and write the message 39 | with codecs.open(file_name, "w", encoding) as file: 40 | try: 41 | file.write(message) 42 | print(f"File created: {file_name}") 43 | except Exception as e: 44 | print(f"Failed to write to {file_name} with encoding {encoding}: {e}") 45 | 46 | print("Files created for each encoding with the custom message.") 47 | -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_ascii.txt: -------------------------------------------------------------------------------- 1 | Hello, world! Greetings, Earth! -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_big5.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_big5.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_euckr.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_euckr.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_gb2312.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_gb2312.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_iso8859-1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_iso8859-1.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_iso8859-5.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_iso8859-5.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_shiftjis.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_shiftjis.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_utf16.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_utf16.txt -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_utf8.txt: -------------------------------------------------------------------------------- 1 | Hello, 世界! Привет, мир! مرحبا بالعالم! -------------------------------------------------------------------------------- /tests/mock_files/various_encodings/message_windows1251.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoxelCubes/DeepQt/cdf01e791ef44fe283d466c65744201bb38c4413/tests/mock_files/various_encodings/message_windows1251.txt -------------------------------------------------------------------------------- /tests/test_backend_interface.py: -------------------------------------------------------------------------------- 1 | from typing import get_type_hints 2 | import deepqt.backends.backend_interface as bi 3 | import deepqt.backends.mock_backend as mb 4 | import deepqt.backends.deepl_backend as db 5 | 6 | 7 | def check_attribute_metadata(some_thing: bi.BackendConfig | bi.BackendStatus): 8 | """ 9 | Test some config class that inherits from BackendConfig or BackendStatus. 10 | (Not a test on its own, but a helper function for other tests.) 11 | """ 12 | meta = some_thing.attribute_metadata() 13 | 14 | # Get attributes of the class and the base class. 15 | attributes = get_type_hints(some_thing) 16 | parent = some_thing.__class__.__bases__[0] 17 | attributes.update(get_type_hints(parent)) 18 | 19 | # Check if all attributes are covered in the metadata. 20 | # for attr in attributes: 21 | # if attr not in meta: 22 | # raise ValueError(f"Attribute {attr} not covered in metadata") 23 | attr_names = list(attributes.keys()) 24 | meta_names = list(meta.keys()) 25 | 26 | # Check for duplicates. 27 | for name in attr_names: 28 | if attr_names.count(name) > 1: 29 | raise ValueError(f"Duplicate attribute name: {name}") 30 | for name in meta_names: 31 | if meta_names.count(name) > 1: 32 | raise ValueError(f"Duplicate metadata name: {name}") 33 | 34 | # Check for missing attributes. 35 | attr_names = set(attr_names) 36 | meta_names = set(meta_names) 37 | in_attr_not_in_meta = attr_names - meta_names 38 | in_meta_not_in_attr = meta_names - attr_names 39 | if in_attr_not_in_meta: 40 | raise ValueError(f"Attributes not covered in metadata: {in_attr_not_in_meta}") 41 | if in_meta_not_in_attr: 42 | raise ValueError(f"Metadata not covered in attributes: {in_meta_not_in_attr}") 43 | 44 | # Check the meta type matches the attribute type. 45 | for name in attr_names & meta_names: 46 | if meta[name].type != attributes[name]: 47 | raise ValueError( 48 | f"Type mismatch for attribute {name}: meta {meta[name].type} != actual {attributes[name]}" 49 | ) 50 | 51 | 52 | def test_mock(): 53 | """ 54 | Test the MockConfig. 55 | """ 56 | check_attribute_metadata(mb.MockConfig()) 57 | 58 | 59 | def test_deepl(): 60 | """ 61 | Test the DeepLConfig. 62 | """ 63 | check_attribute_metadata(db.DeepLConfig()) 64 | -------------------------------------------------------------------------------- /tests/test_encoding_recognition.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import deepqt.utils as ut 3 | from tests.helpers import mock_file_path 4 | from tests.mock_files import various_encodings 5 | 6 | # Define expected messages tailored for each encoding 7 | expected_messages = { 8 | "message_utf8.txt": "Hello, 世界! Привет, мир! مرحبا بالعالم!", 9 | "message_utf16.txt": "Hello, 世界! Привет, мир! مرحبا بالعالم!", 10 | "message_iso8859-1.txt": "Hello, world! Bonjour, le monde! ¡Hola, mundo!", 11 | "message_ascii.txt": "Hello, world! Greetings, Earth!", 12 | "message_shiftjis.txt": "こんにちは、世界!これはShift JISエンコーディングのテキストサンプルです。日本語の文字が正しく表示されることを確認してください。", 13 | "message_gb2312.txt": "你好,世界! Hello, 世界!", 14 | "message_euckr.txt": "안녕하세요, 세계! Hello, world!", 15 | "message_windows1251.txt": "Hello, мир! Привет, world!", 16 | "message_iso8859-5.txt": "Привет, мир! Hello, world!", 17 | "message_big5.txt": "你好,世界! Hello, 世界!", 18 | } 19 | 20 | 21 | @pytest.mark.parametrize("file_name, expected_message", expected_messages.items()) 22 | def test_read_text_encoding(file_name, expected_message): 23 | # Get the file path using mock_file_path 24 | path = mock_file_path(file_name, module=various_encodings) 25 | 26 | # Guess the encoding using the read_text_encoding function 27 | guessed_encoding = ut.detect_encoding(path) 28 | 29 | # Read the file with the guessed encoding 30 | with path.open(encoding=guessed_encoding) as f: 31 | recovered_message = f.read() 32 | 33 | # Assert that the recovered message matches the expected message 34 | actual_encoding = file_name.split("_")[1].split(".")[0] 35 | 36 | def simplify(encoding): 37 | return encoding.lower().replace("-", "").replace("_", "") 38 | 39 | assert simplify(actual_encoding) == simplify( 40 | guessed_encoding 41 | ), f"Failed for encoding in {file_name}: Guessed {guessed_encoding}, got {actual_encoding}" 42 | assert ( 43 | recovered_message == expected_message 44 | ), f"Failed for encoding in {file_name}: Guessed {guessed_encoding}, got {recovered_message}" 45 | 46 | # Running the wrapper version that combines both tests. 47 | with ut.read_autodetect_encoding(path) as f: 48 | recovered_message = f.read() 49 | 50 | assert ( 51 | recovered_message == expected_message 52 | ), f"Failed for encoding in {file_name}: Got {recovered_message}" 53 | -------------------------------------------------------------------------------- /tests/test_log_parser.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | import deepqt.log_parser as lp 3 | from tests.helpers import read_mock_file 4 | import tests.mock_files.logs as log_files 5 | 6 | 7 | def test_parse_good(): 8 | # Test with good_pcleaner.log 9 | logfile = read_mock_file("good.log", module=log_files) 10 | # mock the get_username function to return "testvm" 11 | with patch("deepqt.log_parser.get_username", return_value="testvm"): 12 | sessions = lp.parse_log_file(logfile) 13 | 14 | assert len(sessions) == lp.MAX_SESSIONS 15 | assert sessions[19].criticals == 1 16 | assert sessions[19].errors == 3 17 | assert sessions[1].criticals == 0 18 | assert sessions[1].errors == 1 19 | assert sessions[1].corrupted is False 20 | assert sessions[5].criticals == 0 21 | assert sessions[5].errors == 0 22 | -------------------------------------------------------------------------------- /tests/test_mime_recognition.py: -------------------------------------------------------------------------------- 1 | from tests.helpers import mock_file_path 2 | from pathlib import Path 3 | 4 | import deepqt.utils as ut 5 | import deepqt.constants as ct 6 | from tests.mock_files import mime_types 7 | 8 | 9 | def test_mime_recognition(): 10 | path = mock_file_path("text.txt", module=mime_types) 11 | assert ut.read_mime_type(path) == ct.Formats.TEXT, "Text file not recognized." 12 | 13 | path = mock_file_path("book.epub", module=mime_types) 14 | assert ut.read_mime_type(path) == ct.Formats.EPUB, "EPUB file not recognized." 15 | 16 | path = mock_file_path("document.pdf", module=mime_types) 17 | assert ut.read_mime_type(path) == ct.Formats.PDF, "PDF file not recognized." 18 | 19 | path = mock_file_path("writer.odt", module=mime_types) 20 | assert ut.read_mime_type(path) == ct.Formats.ODT, "ODT file not recognized." 21 | 22 | path = mock_file_path("word.docx", module=mime_types) 23 | assert ut.read_mime_type(path) == ct.Formats.DOCX, "DOCX file not recognized." 24 | 25 | path = mock_file_path("impress.odp", module=mime_types) 26 | assert ut.read_mime_type(path) == ct.Formats.ODP, "ODP file not recognized." 27 | 28 | path = mock_file_path("powerpoint.pptx", module=mime_types) 29 | assert ut.read_mime_type(path) == ct.Formats.PPTX, "PPTX file not recognized." 30 | 31 | path = mock_file_path("calc.ods", module=mime_types) 32 | assert ut.read_mime_type(path) == ct.Formats.ODS, "ODS file not recognized." 33 | 34 | path = mock_file_path("excel.xlsx", module=mime_types) 35 | assert ut.read_mime_type(path) == ct.Formats.XLSX, "XLSX file not recognized." 36 | 37 | path = mock_file_path("website.html", module=mime_types) 38 | assert ut.read_mime_type(path) == ct.Formats.HTML, "HTML file not recognized." 39 | 40 | path = mock_file_path("translation.xliff", module=mime_types) 41 | assert ut.read_mime_type(path) == ct.Formats.XLF, "XLF file not recognized." 42 | 43 | path = mock_file_path("archive.zip", module=mime_types) 44 | assert ut.read_mime_type(path) == ct.Formats.UNKNOWN, "ZIP file recognized as something." 45 | 46 | path = mock_file_path("blank_file") 47 | assert ut.read_mime_type(path) == ct.Formats.UNKNOWN, "Blank file not recognized." 48 | -------------------------------------------------------------------------------- /tests/test_theming.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from pathlib import Path, PosixPath 3 | from importlib import resources 4 | 5 | import deepqt.utils as ut 6 | import deepqt.ui_generated_files 7 | import deepqt.data.theme_icons as theme_icons_module 8 | 9 | 10 | def test_get_available_themes(): 11 | """ 12 | Test the get_available_themes function. 13 | """ 14 | themes = ut.get_available_themes() 15 | 16 | assert themes 17 | assert themes == [("breeze", "Breeze Light"), ("breeze-dark", "Breeze Dark")] 18 | 19 | 20 | def test_theme_icon_presence(): 21 | """ 22 | Test that all icons are present in the resources. 23 | """ 24 | # Read the icon list from the yaml file located at DeepQt/icons/theme_list.yaml 25 | yaml_path = Path(__file__).parent.parent / "icons" / "theme_list.yaml" 26 | with resources.files(theme_icons_module) as theme_icons_path: 27 | theme_icons_root = Path(theme_icons_path) 28 | 29 | with yaml_path.open() as file: 30 | data = yaml.safe_load(file) 31 | 32 | theme_directories = data["Theme directories"] 33 | theme_icons = data["Files"] 34 | 35 | # Perform a depth-first search for each icon in a theme, which means 36 | # checking for leaf nodes, which are the icon file names. 37 | # When coming across a directory, the name of that directory is added to the path, 38 | # then scanned recursively. 39 | for theme_path in theme_directories: 40 | # Only use the last directory name of the theme path. 41 | theme_directory_name = PosixPath(theme_path).name 42 | for category, sizes in theme_icons.items(): 43 | for size, icons in sizes.items(): 44 | for icon in icons: 45 | expected_relative_path = ( 46 | PosixPath(theme_directory_name) / category / str(size) / icon 47 | ) 48 | 49 | for suffix in ["svg", "png"]: 50 | full_path = ( 51 | theme_icons_root 52 | / expected_relative_path.with_suffix(f".{suffix}").as_posix() 53 | ) 54 | if full_path.exists(): 55 | break 56 | else: 57 | assert ( 58 | False 59 | ), f"Icon {expected_relative_path} not found among the theme_icons data files." 60 | 61 | 62 | def test_theme_icon_app_presence(): 63 | # Load the source code and inspect it for xdg icon names. 64 | with resources.files(deepqt.ui_generated_files) as source_dir: 65 | source_dir = Path(source_dir) 66 | 67 | # This only works for the generated code from ui files. 68 | # General python code would be undecidable. 69 | expected_icons: set[str] = set() 70 | for source_file in source_dir.rglob("*.py"): 71 | with source_file.open() as file: 72 | for line in file: 73 | if 'iconThemeName = u"' in line: 74 | theme_name = line.split('iconThemeName = u"')[1].split('"')[0] 75 | expected_icons.add(theme_name) 76 | 77 | # Ensure they are listed in the yaml icon list file. 78 | yaml_path = Path(__file__).parent.parent / "icons" / "theme_list.yaml" 79 | with yaml_path.open() as file: 80 | data = yaml.safe_load(file) 81 | 82 | theme_icons = data["Files"] 83 | icon_set = set() 84 | for category, sizes in theme_icons.items(): 85 | for size, icons in sizes.items(): 86 | for icon in icons: 87 | icon_set.add(icon) 88 | 89 | missing = expected_icons - icon_set 90 | assert not missing 91 | -------------------------------------------------------------------------------- /tests/test_writing_config.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from loguru import logger 4 | 5 | from tests.helpers import mock_file_path 6 | from tests import mock_files 7 | import tests.mock_files.config as config_files 8 | 9 | import deepqt.config as cfg 10 | import deepqt.constants as ct 11 | 12 | # Suppress the loguru logger. 13 | logger.remove() 14 | 15 | 16 | def test_un_structuring(): 17 | # We shouldn't find any attributes that have the "no_save" metadata flag set. 18 | # All other attributes should be present. 19 | 20 | config: cfg.Config = cfg.Config() 21 | 22 | dump: dict = config.dump() 23 | 24 | # First check the top level attributes. 25 | assert dump["gui_theme"] == config.gui_theme 26 | assert dump["lang_from"] == config.lang_from 27 | assert dump["lang_to"] == config.lang_to 28 | assert dump["current_backend"] == config.current_backend 29 | 30 | # Check all backend configs. 31 | for backend, bconf in config.backend_configs.items(): 32 | backend_dump = dump["backend_configs"][backend] 33 | metadata = bconf.attribute_metadata() 34 | 35 | for attr, meta in metadata.items(): 36 | if meta.no_save: 37 | assert attr not in backend_dump 38 | else: 39 | assert backend_dump[attr] == getattr(bconf, attr) 40 | -------------------------------------------------------------------------------- /ui_files/IssueReporter.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | IssueReporter 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1200 10 | 700 11 | 12 | 13 | 14 | Report an Issue 15 | 16 | 17 | 18 | 6 19 | 20 | 21 | 22 | 23 | Consider including the log for the session that had an issue, if applicable. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | View Logs: 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 12 52 | 53 | 54 | 55 | 56 | 57 | 0 58 | 0 59 | 60 | 61 | 62 | Log file is at: 63 | 64 | 65 | 66 | 67 | 68 | 69 | <log file path> 70 | 71 | 72 | Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | <name was hidden> 84 | 85 | 86 | 87 | 88 | 89 | 90 | Qt::Horizontal 91 | 92 | 93 | 94 | 40 95 | 20 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Copy to Clipboard 104 | 105 | 106 | 107 | .. 108 | 109 | 110 | 111 | 112 | 113 | 114 | Open Issue Tracker 115 | 116 | 117 | 118 | .. 119 | 120 | 121 | 122 | 123 | 124 | 125 | Close 126 | 127 | 128 | 129 | .. 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | LogViewer 140 | QPlainTextEdit 141 |
deepqt.log_viewer
142 |
143 | 144 | CComboBox 145 | QComboBox 146 |
deepqt.CustomQ.CComboBox
147 |
148 |
149 | 150 | 151 |
152 | -------------------------------------------------------------------------------- /ui_files/api_key_deepl.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog_API 4 | 5 | 6 | 7 | 0 8 | 0 9 | 450 10 | 200 11 | 12 | 13 | 14 | Account Settings 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | API Key: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 100 30 | 31 | 32 | 33 | 34 | 35 | 36 | Account Type: 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | DeepL API FREE 45 | 46 | 47 | 48 | 49 | DeepL API PRO 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::Horizontal 60 | 61 | 62 | QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | buttonBox 72 | accepted() 73 | Dialog_API 74 | accept() 75 | 76 | 77 | 248 78 | 254 79 | 80 | 81 | 157 82 | 274 83 | 84 | 85 | 86 | 87 | buttonBox 88 | rejected() 89 | Dialog_API 90 | reject() 91 | 92 | 93 | 316 94 | 260 95 | 96 | 97 | 286 98 | 274 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ui_files/api_key_input.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog_API 4 | 5 | 6 | 7 | 0 8 | 0 9 | 450 10 | 141 11 | 12 | 13 | 14 | API Key 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | API Key: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 100 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Qt::Horizontal 39 | 40 | 41 | QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | buttonBox 51 | accepted() 52 | Dialog_API 53 | accept() 54 | 55 | 56 | 248 57 | 254 58 | 59 | 60 | 157 61 | 274 62 | 63 | 64 | 65 | 66 | buttonBox 67 | rejected() 68 | Dialog_API 69 | reject() 70 | 71 | 72 | 316 73 | 260 74 | 75 | 76 | 286 77 | 274 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /ui_files/api_overview_remote.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | api_overview_deepl 4 | 5 | 6 | 7 | 0 8 | 0 9 | 405 10 | 66 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | 34 | Status: 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | <okay_icon> 44 | 45 | 46 | 47 | 48 | 49 | 50 | Okay 51 | 52 | 53 | 54 | 55 | 56 | 57 | <error_icon> 58 | 59 | 60 | 61 | 62 | 63 | 64 | Error 65 | 66 | 67 | 68 | 69 | 70 | 71 | Qt::Horizontal 72 | 73 | 74 | 75 | 0 76 | 10 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Refresh 85 | 86 | 87 | 88 | 89 | 90 | 91 | .. 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Usage: 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | <error_icon> 113 | 114 | 115 | 116 | 117 | 118 | 119 | <warn_icon> 120 | 121 | 122 | 123 | 124 | 125 | 126 | <usage_placeholder> 127 | 128 | 129 | 130 | 131 | 132 | 133 | Qt::Horizontal 134 | 135 | 136 | 137 | 40 138 | 20 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /ui_files/epub_preview.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | EpubPreview 4 | 5 | 6 | 7 | 0 8 | 0 9 | 720 10 | 800 11 | 12 | 13 | 14 | Placeholder 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 6 22 | 23 | 24 | 0 25 | 26 | 27 | 28 | 29 | 30 | 31 | Original 32 | 33 | 34 | true 35 | 36 | 37 | 38 | 39 | 40 | 41 | Glossary 42 | 43 | 44 | 45 | 46 | 47 | 48 | Translated 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 0 60 | 61 | 62 | 0 63 | 64 | 65 | 0 66 | 67 | 68 | 0 69 | 70 | 71 | 0 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 0 82 | 83 | 84 | 0 85 | 86 | 87 | 0 88 | 89 | 90 | 0 91 | 92 | 93 | 0 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 0 104 | 105 | 106 | 0 107 | 108 | 109 | 0 110 | 111 | 112 | 0 113 | 114 | 115 | 0 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 0 129 | 0 130 | 131 | 132 | 133 | Save Preview to File 134 | 135 | 136 | 137 | .. 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /ui_files/text_preview.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TextPreview 4 | 5 | 6 | 7 | 0 8 | 0 9 | 720 10 | 800 11 | 12 | 13 | 14 | Placeholder 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 28 | 29 | false 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 0 38 | 0 39 | 40 | 41 | 42 | Save Preview to File 43 | 44 | 45 | 46 | .. 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | --------------------------------------------------------------------------------