Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch port_pyside6
Excluding Merge-Ins
This is equivalent to a diff from
cf0e6f4403
to 8b1031c310
2024-10-18
| | |
15:54 |
|
check-in: c9928b0eb7 user: thomas tags: trunk
|
2024-10-16
| | |
11:48 |
|
Leaf
check-in: 8b1031c310 user: thomas tags: port_pyside6
|
2024-10-09
| | |
13:08 |
|
check-in: cf0e6f4403 user: thomas tags: trunk
|
2024-10-03
| | |
14:45 |
|
check-in: 40729f514a user: thomas tags: trunk
|
2024-09-14
| | |
12:57 |
|
check-in: 45b2a6f419 user: thomas tags: port_pyside6
|
| | |
Changes to README.md.
︙ | | |
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
-
+
|
- Python >= 3.8
These external libraries are used in the code. They can be installed from PyPI.
- `platformdirs`
- `ijson`
- `pint`
- `PyQt5`
- `PySide6`
- `delegateto`
- `PyHamcrest`
- `cx_Freeze` (Stand-alone bundles only. Used by the installer for Windows®-based platforms.)
### System libraries
- `SQLite3` >= 3.35.0
|
︙ | | |
Changes to build_MTGProxyPrinter_packages.bat.
1
2
3
4
5
6
7
8
9
10
11
12
|
1
2
3
4
5
6
7
8
9
10
11
12
|
-
-
+
+
|
:: Generate an application bundle using cx_Freeze
:: Create or activate the build environment
IF EXIST "venv" (
call venv\Scripts\activate.bat
IF EXIST "venv-PySide6" (
call venv-PySide6\Scripts\activate.bat
) ELSE (
call create_development_environment.bat
)
:: Create a platform-dependent, portable build in the build directory
:: and an MSI-based installer in the dist directory.
:: Also creates a cross-platform Python sdist and wheel package in the dist directory.
|
︙ | | |
Changes to build_MTGProxyPrinter_packages.sh.
1
2
3
4
5
6
7
8
9
|
1
2
3
4
5
6
7
8
9
|
-
+
|
#!/bin/bash
ENVIRONMENT_NAME="venv"
ENVIRONMENT_NAME="venv-PySide6"
# Generate an application bundle using cx_Freeze for Linux.
if [ ! -e "${ENVIRONMENT_NAME}" ]; then
./create_development_environment.sh
fi
source "${ENVIRONMENT_NAME}/bin/activate"
|
︙ | | |
Changes to create_development_environment.bat.
1
2
3
4
5
6
7
8
9
10
|
1
2
3
4
5
6
7
8
9
10
|
-
+
-
+
|
python -m venv venv
python -m venv venv-PySide6
call venv\Scripts\activate.bat
call venv-PySide6\Scripts\activate.bat
python -m pip install --upgrade pip
python -m pip install wheel
python -m pip install "pip-tools >= 7"
python -m piptools compile -o requirements.txt pyproject.toml
python -m piptools compile --extra dev -o requirements-dev.txt pyproject.toml
python -m piptools compile --extra package -o requirements-package.txt pyproject.toml
|
︙ | | |
Changes to create_development_environment.sh.
1
2
3
4
5
6
7
8
9
|
1
2
3
4
5
6
7
8
9
|
-
+
|
#!/bin/bash
ENVIRONMENT_NAME="venv"
ENVIRONMENT_NAME="venv-PySide6"
if [ -e "${ENVIRONMENT_NAME}" ]; then
echo "Removing already existing virtual environment."
rm -r "${ENVIRONMENT_NAME}"
fi
python -m venv "${ENVIRONMENT_NAME}"
|
︙ | | |
Changes to doc/ThirdPartyLicenses.md.
︙ | | |
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
|
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
|
-
+
-
+
-
+
-
-
+
-
-
|
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
# PyQt5
# Qt6, PySide6
Copyright (c) [Riverbank Computing Limited](https://www.riverbankcomputing.com/).
Qt and PySide6 are available under the GNU Lesser General Public License version 3.
The included copy is licensed under the terms of the
The Qt Toolkit is Copyright (C) 2018 The Qt Company Ltd. and other contributors.
GNU General Public License version 3 as published by the Free Software Foundation.
Contact: https://www.qt.io/licensing/
See LICENSE.md (when viewing from the source code archive) or the License tab in the
MTGProxyPrinter About dialog for details.
# cx_Freeze
Note: This section only applies to application bundles and installers created
using cx_Freeze, like the stand-alone installer provided for Windows®-based platforms.
|
︙ | | |
Changes to mtg_proxy_printer/__main__.py.
︙ | | |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
+
-
|
# Import and implicitly load the settings first, before importing any modules that pull in GUI classes.
import mtg_proxy_printer.settings
import os
import platform
import sys
from PyQt5.QtCore import Qt, QTimer
from PySide6.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
import mtg_proxy_printer.app_dirs
from mtg_proxy_printer.argument_parser import parse_args
import mtg_proxy_printer.logger
from mtg_proxy_printer.application import Application
import mtg_proxy_printer.natsort
|
︙ | | |
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
-
-
-
-
+
|
def main():
global _app
arguments = parse_args()
mtg_proxy_printer.app_dirs.migrate_from_old_appdirs()
mtg_proxy_printer.logger.configure_root_logger()
handle_ssl_certificates()
# According to https://doc.qt.io/qt-5/qt.html#ApplicationAttribute-enum,
# Qt.AA_EnableHighDpiScaling has to be set prior to creating the QApplication instance
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
_app = Application(arguments, sys.argv)
if arguments.test_exit_on_launch:
logger.info("Skipping startup tasks, because immediate application exit was requested.")
QTimer.singleShot(0, _app.main_window.on_action_quit_triggered)
else:
logger.debug("Enqueueing startup tasks.")
_app.enqueue_startup_tasks(arguments)
logger.debug("Initialisation done. Starting event loop.")
_app.exec_()
_app.exec()
logger.debug("Left event loop.")
if __name__ == "__main__":
main()
|
Changes to mtg_proxy_printer/application.py.
︙ | | |
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
-
-
-
+
+
+
|
import pathlib
import platform
import shutil
import sys
from tempfile import mkdtemp
import typing
from PyQt5.QtCore import pyqtSlot as Slot, Qt, QTimer, QStringListModel, QThreadPool, QTranslator, QLocale, QLibraryInfo
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QIcon
from PySide6.QtCore import Slot, QTimer, QStringListModel, QThreadPool, QTranslator, QLocale, QLibraryInfo
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QIcon
from mtg_proxy_printer.argument_parser import Namespace
from mtg_proxy_printer import meta_data
import mtg_proxy_printer.model.carddb
import mtg_proxy_printer.carddb_migrations
import mtg_proxy_printer.model.document
import mtg_proxy_printer.model.imagedb
|
︙ | | |
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
-
-
-
-
-
-
+
+
+
+
+
+
+
|
class Application(QApplication):
def __init__(self, args: Namespace, argv: typing.List[str] = None):
if argv is None:
argv = sys.argv
logger.info(f"Starting MTGProxyPrinter version {meta_data.__version__}")
if not os.getenv("QT_QPA_PLUGIN") and "-platform" not in argv and platform.system() == "Windows":
logger.info("Running on Windows without explicit platform override. Enabling dark mode rendering.")
# The explicit set platform and parameters overwrite the environment, so set these options iff neither
# present as parameters nor environment variables.
argv.append("-platform")
argv.append("windows:darkmode=1")
super().__init__(argv)
# Note: The sub-expression '"-style" not in argv' breaks, if "-style" is passed as a value for another
# argument or as a positional argument.
# (For example, if the user wants to load a document with file name "-style" via command line argument.)
if platform.system() == "Windows" and "-style" not in argv and not os.getenv("QT_STYLE_OVERRIDE"):
logger.info("Running on Windows without explicit style set. Use 'fusion', which supports dark mode.")
# Set a dark-mode compatible style, if on Windows and the user does not override the style.
self.setStyle("fusion")
# Used by the with_database_write_lock decorator to not start un-frozen,
# waiting tasks when the application is about to exit
self.should_run = True
self.args = args
self._setup_translations()
self._setup_icons()
self.language_model = self._create_language_model() # TODO: Can this be removed?
|
︙ | | |
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
|
-
+
|
logger.info(
f"Loading localisations. System locale: {system_locale.name()}, selected locale: {locale.name()}. "
f"Possible display languages are: {locale.uiLanguages()}")
path = ":" if mtg_proxy_printer.ui.common.HAS_COMPILED_RESOURCES \
else str(pathlib.Path(mtg_proxy_printer.__file__).parent / "resources")
path += "/translations"
logger.debug(f"Locale search path is '{path}'")
self._load_translator(locale, "qtbase", QLibraryInfo.location(QLibraryInfo.LibraryLocation.TranslationsPath))
self._load_translator(locale, "qtbase", QLibraryInfo.location(QLibraryInfo.LibraryPath.TranslationsPath))
self._load_translator(locale, "mtgproxyprinter", path)
def _load_translator(self, locale: QLocale, component: str, path: str):
translator = QTranslator(self)
if translator.load(locale, component, '_', path):
logger.debug(f"{component} translation loaded successfully, installing it")
self.installTranslator(translator)
|
︙ | | |
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
-
-
|
theme_search_paths = QIcon.themeSearchPaths()
theme_search_paths.append(mtg_proxy_printer.ui.common.ICON_PATH_PREFIX)
QIcon.setThemeSearchPaths(theme_search_paths)
QIcon.setThemeName("breeze")
else:
logger.debug(f"Using system-provided icon theme '{fallback_icon_theme}'")
self.setAttribute(Qt.AA_UseHighDpiPixmaps)
@Slot()
def quit(self):
logger.info("About to exit.")
self.should_run = False
self.main_window.hide()
self.main_window.close()
self.closeAllWindows()
|
︙ | | |
Changes to mtg_proxy_printer/card_info_downloader.py.
︙ | | |
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
-
+
|
import socket
import typing
import urllib.error
import urllib.parse
import urllib.request
import ijson
from PyQt5.QtCore import pyqtSignal as Signal, QObject, Qt, QThreadPool
from PySide6.QtCore import Signal, QObject, Qt, QThreadPool
from mtg_proxy_printer.downloader_base import DownloaderBase
from mtg_proxy_printer.model.carddb import CardDatabase, SCHEMA_NAME, with_database_write_lock
from mtg_proxy_printer.sqlite_helpers import cached_dedent
from mtg_proxy_printer.printing_filter_updater import PrintingFilterUpdater
import mtg_proxy_printer.metered_file
from mtg_proxy_printer.logger import get_logger
|
︙ | | |
Changes to mtg_proxy_printer/carddb_migrations.py.
︙ | | |
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
-
+
|
import time
import typing
import urllib.error
import urllib.parse
from textwrap import dedent
from typing import List, Dict, Union, Tuple, Any, Generator, Callable
from PyQt5.QtCore import QCoreApplication, Qt
from PySide6.QtCore import QCoreApplication, Qt
try:
from typing import LiteralString
except AttributeError:
from typing_extensions import LiteralString
from mtg_proxy_printer.progress_meter import ProgressMeter
|
︙ | | |
Changes to mtg_proxy_printer/decklist_downloader.py.
︙ | | |
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
-
+
|
import urllib.parse
from io import StringIO
import platform
import re
import typing
import ijson
from PyQt5.QtGui import QValidator
from PySide6.QtGui import QValidator
from mtg_proxy_printer.downloader_base import DownloaderBase
from mtg_proxy_printer.decklist_parser.common import ParserBase
from mtg_proxy_printer.decklist_parser.csv_parsers import ScryfallCSVParser, TappedOutCSVParser
from mtg_proxy_printer.decklist_parser.re_parsers import MTGArenaParser, MagicWorkstationDeckDataFormatParser, \
XMageParser
from mtg_proxy_printer.logger import get_logger
|
︙ | | |
Changes to mtg_proxy_printer/decklist_parser/common.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from abc import abstractmethod
import typing
from PyQt5.QtCore import QObject, pyqtSignal as Signal
from PySide6.QtCore import QObject, Signal
from mtg_proxy_printer.model.carddb import Card, CardDatabase, CardIdentificationData
from mtg_proxy_printer.model.imagedb import ImageDatabase
import mtg_proxy_printer.settings
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
|
︙ | | |
Changes to mtg_proxy_printer/decklist_parser/csv_parsers.py.
︙ | | |
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
+
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
import collections
import csv
import typing
from PyQt5.QtCore import QObject, QCoreApplication
from PySide6.QtCore import QObject, QCoreApplication
from mtg_proxy_printer.model.carddb import Card, CardDatabase, CardIdentificationData
from mtg_proxy_printer.model.imagedb import ImageDatabase
from .common import ParsedDeck, ParserBase
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
|
︙ | | |
Changes to mtg_proxy_printer/decklist_parser/re_parsers.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import copy
from collections import Counter
import re
import typing
from PyQt5.QtCore import QObject, QCoreApplication
from PySide6.QtCore import QObject, QCoreApplication
from mtg_proxy_printer.decklist_parser.common import ParsedDeck, ParserBase
from mtg_proxy_printer.model.carddb import Card, CardDatabase, CardIdentificationData
from mtg_proxy_printer.model.imagedb import ImageDatabase
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
|
︙ | | |
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
-
-
-
+
+
+
+
|
class MagicWorkstationDeckDataFormatParser(GenericRegularExpressionDeckParser):
@staticmethod
def supported_file_types() -> typing.Dict[str, typing.List[str]]:
return {
QCoreApplication.translate(
"MagicWorkstationDeckDataFormatParser", "Magic Workstation Deck Data Format"): ["mwDeck"],
}
QCoreApplication.translate(
"MagicWorkstationDeckDataFormatParser", "Magic Workstation Deck Data Format"): ["mwDeck"],
}
PREFIXES_TO_SKIP = frozenset({"//"})
def __init__(self, card_db: CardDatabase, image_db: ImageDatabase, parent: QObject = None):
super().__init__(
card_db, image_db,
re.compile(r"(SB: {2})?(?P<copies>\d+) \[(?P<set_code>\w+)?] (?P<name>.+)"), parent
)
|
︙ | | |
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
-
+
+
|
A parser for XMage deck files (file extension ".dck").
"""
@staticmethod
def supported_file_types() -> typing.Dict[str, typing.List[str]]:
return {
QCoreApplication.translate("XMageParser", "XMage Deck file"): ["dck"],
}
}
PREFIXES_TO_SKIP = frozenset(("NAME", "LAYOUT"))
def __init__(self, card_db: CardDatabase, image_db: ImageDatabase, parent: QObject = None):
super().__init__(
card_db, image_db,
re.compile(r"(SB: )?(?P<copies>\d+) \[(?P<set_code>\w+):(?P<collector_number>[^]]+)] (?P<name>.+)"), parent
)
|
Changes to mtg_proxy_printer/document_controller/_interface.py.
︙ | | |
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
-
+
|
from abc import abstractmethod
from functools import partial
import itertools
import operator
import typing
from PyQt5.QtCore import QCoreApplication
from PySide6.QtCore import QCoreApplication
from mtg_proxy_printer.units_and_sizes import StringList
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.document import Document
try:
|
︙ | | |
Changes to mtg_proxy_printer/document_controller/move_cards.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import functools
import itertools
import typing
from PyQt5.QtCore import QModelIndex
from PySide6.QtCore import QModelIndex
from ._interface import DocumentAction, IllegalStateError, Self
from mtg_proxy_printer.logger import get_logger
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.document_page import Page
from mtg_proxy_printer.model.document import Document
|
︙ | | |
Changes to mtg_proxy_printer/document_controller/replace_card.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import functools
import typing
from PyQt5.QtCore import Qt
from PySide6.QtCore import Qt
from mtg_proxy_printer.model.carddb import Card
from mtg_proxy_printer.model.card_list import PageColumns
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.document_page import CardContainer
from mtg_proxy_printer.model.document import Document
|
︙ | | |
Changes to mtg_proxy_printer/document_controller/shuffle_document.py.
︙ | | |
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
+
|
from random import randbytes
except ImportError:
# Compatibility with Py 3.8
from secrets import token_bytes as randbytes
import typing
from PyQt5.QtCore import Qt, QModelIndex
from PySide6.QtCore import Qt, QModelIndex
from ._interface import DocumentAction, IllegalStateError, Self
from mtg_proxy_printer.model.carddb import Card
from mtg_proxy_printer.model.card_list import PageColumns
from mtg_proxy_printer.model.document_page import CardContainer
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.units_and_sizes import PageType
|
︙ | | |
Changes to mtg_proxy_printer/downloader_base.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gzip
from PyQt5.QtCore import QObject, pyqtSignal as Signal
from PySide6.QtCore import QObject, Signal
import mtg_proxy_printer.http_file
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
# Offer accepting gzip, as that is supported by the Scryfall server and reduces network data use by 80-90%
|
︙ | | |
Changes to mtg_proxy_printer/http_file.py.
︙ | | |
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
+
|
import socket
import time
import typing
from typing import List, Optional, Dict
import urllib.error
import urllib.request
from PyQt5.QtCore import QObject, pyqtSignal as Signal
from PySide6.QtCore import QObject, Signal
import delegateto
from mtg_proxy_printer.meta_data import USER_AGENT
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
|
︙ | | |
Changes to mtg_proxy_printer/meta_data.py.
︙ | | |
10
11
12
13
14
15
16
17
18
19
20
21
22
|
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-
+
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
PROGRAMNAME = "MTGProxyPrinter"
__version__ = "0.29.1"
__version__ = "0.29.1+PySide6"
COPYRIGHT = "(C) 2020-2024 Thomas Hess"
HOME_PAGE = "https://chiselapp.com/user/luziferius/repository/MTGProxyPrinter"
DOWNLOAD_WEB_PAGE = f"{HOME_PAGE}/uv/download.html"
USER_AGENT = f"{PROGRAMNAME}/{__version__} ({HOME_PAGE})"
|
Changes to mtg_proxy_printer/metered_file.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Iterable, List, Optional, BinaryIO, Union
from io import BufferedIOBase
from PyQt5.QtCore import QObject, pyqtSignal as Signal
from PySide6.QtCore import QObject, Signal
from delegateto import delegate
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
__all__ = [
|
︙ | | |
Changes to mtg_proxy_printer/missing_images_manager.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing
from PyQt5.QtCore import QObject, pyqtSignal as Signal, pyqtSlot as Slot
from PySide6.QtCore import QObject, Signal, Slot
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
__all__ = [
|
︙ | | |
Changes to mtg_proxy_printer/model/card_list.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
-
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import enum
import itertools
import typing
from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSlot as Slot, pyqtSignal as Signal, QItemSelection
from PyQt5.QtGui import QIcon
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal, QItemSelection
from PySide6.QtGui import QIcon
from mtg_proxy_printer.model.carddb import Card, CardIdentificationData, CardDatabase, AnyCardType
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
CardList = typing.List[Card]
|
︙ | | |
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
-
+
|
return self.tr("Front") if card.is_front else self.tr("Back")
if card.is_oversized:
if role == ItemDataRole.ToolTipRole:
return self.tr("Beware: Potentially oversized card!\nThis card may not fit in your deck.")
elif role == ItemDataRole.DecorationRole:
return self._oversized_icon
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
def flags(self, index: QModelIndex) -> ItemFlag:
flags = super().flags(index)
if index.column() in self.EDITABLE_COLUMNS:
flags |= ItemFlag.ItemIsEditable
return flags
def setData(self, index: QModelIndex, value: typing.Any, role: ItemDataRole = ItemDataRole.EditRole) -> bool:
column = index.column()
|
︙ | | |
Changes to mtg_proxy_printer/model/carddb.py.
︙ | | |
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
-
-
+
+
+
|
import itertools
import functools
import pathlib
import sqlite3
import threading
import typing
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPixmap, QColor, QTransform, QPainter, QColorConstants
from PyQt5.QtCore import Qt, QPoint, QRect, QSize, QPointF, QObject, pyqtSignal as Signal, pyqtSlot as Slot
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QPixmap, QColor, QTransform, QPainter, QColorConstants
from PySide6.QtCore import Qt, QPoint, QRect, QSize, QPointF, QObject, Signal, Slot
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.imagedb import CacheContent
import mtg_proxy_printer.app_dirs
from mtg_proxy_printer.natsort import natural_sorted
import mtg_proxy_printer.meta_data
|
︙ | | |
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
-
+
|
sample_area = self.image_file.copy(QRect(
QPoint(
round(self.image_file.width() * corner.value[0]),
round(self.image_file.height() * corner.value[1])),
QSize(10, 10)
))
average_color = sample_area.scaled(
1, 1, transformMode=Qt.TransformationMode.SmoothTransformation).toImage().pixelColor(0, 0)
1, 1, mode=Qt.TransformationMode.SmoothTransformation).toImage().pixelColor(0, 0)
return average_color
def display_string(self):
return f'"{self.name}" [{self.set.code.upper()}:{self.collector_number}]'
@property
def set_code(self):
|
︙ | | |
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
-
+
|
# Cards thus can’t be scaled using a singular factor of sqrt(2) on both axis.
# The scaled cards get a bit compressed horizontally.
vertical_scaling_factor = card_size.width() / card_size.height()
horizontal_scaling_factor = card_size.height()/(card_size.width()*2)
combined_image = QPixmap(card_size)
combined_image.fill(QColor.fromRgb(255, 255, 255, 0)) # Fill with fully transparent white
painter = QPainter(combined_image)
painter.setRenderHints(RenderHint.SmoothPixmapTransform | RenderHint.HighQualityAntialiasing)
painter.setRenderHints(RenderHint.SmoothPixmapTransform | RenderHint.Antialiasing)
transformation = QTransform()
transformation.rotate(90)
transformation.scale(horizontal_scaling_factor, vertical_scaling_factor)
painter.setTransform(transformation)
painter.drawPixmap(QPointF(card_size.width(), -card_size.height()), self.back.image_file)
painter.drawPixmap(QPointF(0, -card_size.height()), self.front.image_file)
|
︙ | | |
Changes to mtg_proxy_printer/model/document.py.
︙ | | |
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
-
+
|
import itertools
import math
import pathlib
import sys
import textwrap
import typing
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSlot as Slot, pyqtSignal as Signal, \
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt, Slot, Signal, \
QPersistentModelIndex
import mtg_proxy_printer.sqlite_helpers
from mtg_proxy_printer.model.document_page import CardContainer, Page, PageList
from mtg_proxy_printer.units_and_sizes import PageType
from mtg_proxy_printer.model.carddb import AnyCardType, CardDatabase, CardIdentificationData
from mtg_proxy_printer.model.card_list import PageColumns
|
︙ | | |
Changes to mtg_proxy_printer/model/document_loader.py.
︙ | | |
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
-
-
+
+
|
import pathlib
import sqlite3
import textwrap
import typing
from unittest.mock import patch
import pint
from PyQt5.QtGui import QPageLayout, QPageSize
from PyQt5.QtCore import QObject, pyqtSignal as Signal, QThreadPool, QMarginsF, QSizeF, Qt
from PySide6.QtGui import QPageLayout, QPageSize
from PySide6.QtCore import QObject, Signal, QThreadPool, QMarginsF, QSizeF, Qt
from hamcrest import assert_that, all_of, instance_of, greater_than_or_equal_to, matches_regexp, is_in, \
has_properties, is_, any_of
try:
from hamcrest import contains_exactly
except ImportError:
# Compatibility with PyHamcrest < 1.10
|
︙ | | |
Changes to mtg_proxy_printer/model/imagedb.py.
︙ | | |
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
-
-
+
+
|
import shutil
import socket
import string
import threading
import typing
import urllib.error
from PyQt5.QtCore import QObject, pyqtSignal as Signal, pyqtSlot as Slot, QModelIndex, Qt, QThreadPool
from PyQt5.QtGui import QPixmap, QColorConstants
from PySide6.QtCore import QObject, Signal, Slot, QModelIndex, Qt, QThreadPool
from PySide6.QtGui import QPixmap, QColorConstants
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.carddb import with_database_write_lock
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard
from mtg_proxy_printer.document_controller.replace_card import ActionReplaceCard
|
︙ | | |
Changes to mtg_proxy_printer/model/string_list.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing
from PyQt5.QtCore import QAbstractListModel, Qt, QObject, QModelIndex
from PySide6.QtCore import QAbstractListModel, Qt, QObject, QModelIndex
from mtg_proxy_printer.model.carddb import MTGSet
__all__ = [
"PrettySetListModel",
]
|
︙ | | |
Changes to mtg_proxy_printer/natsort.py.
︙ | | |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
+
|
"""
Natural sorting for lists or other iterables of strings.
"""
import re
import typing
from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex
from PySide6.QtCore import QSortFilterProxyModel, QModelIndex
__all__ = [
"natural_sorted",
"str_less_than",
"NaturallySortedSortFilterProxyModel",
]
|
︙ | | |
Changes to mtg_proxy_printer/print.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
-
-
+
+
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import math
from pathlib import Path
from PyQt5.QtCore import QObject, QMarginsF, QSizeF, pyqtSlot as Slot, QPersistentModelIndex
from PyQt5.QtGui import QPainter, QPdfWriter, QPageSize
from PyQt5.QtPrintSupport import QPrinter
from PySide6.QtCore import QObject, QMarginsF, QSizeF, Slot, QPersistentModelIndex
from PySide6.QtGui import QPainter, QPdfWriter, QPageSize
from PySide6.QtPrintSupport import QPrinter
import mtg_proxy_printer.meta_data
from mtg_proxy_printer.settings import settings
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.document_loader import PageLayoutSettings
from mtg_proxy_printer.ui.page_scene import RenderMode, PageScene
from mtg_proxy_printer.logger import get_logger
|
︙ | | |
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
-
|
f"Setting page layout failed! "
f"Layout: page_size={page_layout.pageSize().size(QPageSize.Unit.Millimeter)}, "
f"orientation={page_layout.orientation()}, "
f"margins={layout.margin_left, layout.margin_top, layout.margin_right, layout.margin_bottom}")
# magnitude returns a float by default, so round to int to avoid a TypeError
printer.setResolution(round(mtg_proxy_printer.units_and_sizes.RESOLUTION.magnitude))
# Disable duplex printing by default
printer.setDoubleSidedPrinting(False)
printer.setDuplex(QPrinter.DuplexMode.DuplexNone)
printer.setOutputFormat(QPrinter.OutputFormat.NativeFormat)
if RenderMode.IMPLICIT_MARGINS not in renderer.render_mode:
printer.setFullPage(True)
return printer
|
︙ | | |
Changes to mtg_proxy_printer/printing_filter_updater.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sqlite3
import typing
from PyQt5.QtCore import QObject, pyqtSignal as Signal, Qt, QCoreApplication
from PySide6.QtCore import QObject, Signal, Qt, QCoreApplication
import mtg_proxy_printer.settings
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.carddb import CardDatabase
from mtg_proxy_printer.ui.main_window import MainWindow
from mtg_proxy_printer.model.carddb import SCHEMA_NAME, with_database_write_lock
from mtg_proxy_printer.sqlite_helpers import cached_dedent, open_database
|
︙ | | |
Changes to mtg_proxy_printer/resources/ui/central_widget/columnar.ui.
︙ | | |
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
+
|
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="renderHints">
<set>QPainter::Antialiasing|QPainter::HighQualityAntialiasing</set>
<set>QPainter::Antialiasing</set>
</property>
</widget>
</item>
<item row="4" column="2" rowspan="2">
<widget class="QTableView" name="page_card_table_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
︙ | | |
Changes to mtg_proxy_printer/resources/ui/central_widget/grouped.ui.
︙ | | |
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
+
|
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="renderHints">
<set>QPainter::Antialiasing|QPainter::HighQualityAntialiasing</set>
<set>QPainter::Antialiasing</set>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="7" colspan="2">
<widget class="QListView" name="document_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
︙ | | |
Changes to mtg_proxy_printer/resources/ui/document_settings_dialog.ui.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
1
2
3
4
5
6
7
8
9
10
11
|
-
-
-
-
-
-
-
-
|
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DocumentSettingsDialog</class>
<widget class="QDialog" name="DocumentSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>501</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Set Document settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="PageConfigContainer" name="page_config_container" native="true"/>
</item>
|
︙ | | |
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>DocumentSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>DocumentSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
|
Changes to mtg_proxy_printer/runner.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal as Signal
from PySide6.QtCore import QRunnable, QObject, Signal
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
__all__ = [
"Runnable",
|
︙ | | |
Changes to mtg_proxy_printer/settings.py.
︙ | | |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
-
+
|
import math
import pathlib
import re
import typing
import tokenize
import pint
from PyQt5.QtCore import QStandardPaths
from PySide6.QtCore import QStandardPaths
import mtg_proxy_printer.app_dirs
import mtg_proxy_printer.meta_data
import mtg_proxy_printer.natsort
from mtg_proxy_printer.units_and_sizes import CardSizes, ConfigParser, SectionProxy, unit_registry, T, QuantityT, UnitT
__all__ = [
|
︙ | | |
Changes to mtg_proxy_printer/ui/add_card.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
-
-
+
+
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Union, Type, Optional
from PyQt5.QtCore import QStringListModel, pyqtSlot as Slot, pyqtSignal as Signal, Qt, QItemSelectionModel, QItemSelection
from PyQt5.QtWidgets import QWidget, QDialogButtonBox
from PyQt5.QtGui import QIcon
from PySide6.QtCore import QStringListModel, Slot, Signal, Qt, QItemSelectionModel, QItemSelection
from PySide6.QtWidgets import QWidget, QDialogButtonBox
from PySide6.QtGui import QIcon
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard
import mtg_proxy_printer.model.string_list
import mtg_proxy_printer.model.carddb
import mtg_proxy_printer.model.document
import mtg_proxy_printer.settings
from mtg_proxy_printer.ui.common import load_ui_from_file
|
︙ | | |
Changes to mtg_proxy_printer/ui/cache_cleanup_wizard.py.
︙ | | |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
-
-
-
+
+
+
|
import datetime
import enum
import functools
import math
import pathlib
import typing
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex, QObject, QBuffer, QIODevice, QItemSelectionModel, QSize
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QWidget, QWizard, QWizardPage
from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex, QObject, QBuffer, QIODevice, QItemSelectionModel, QSize
from PySide6.QtGui import QIcon, QPixmap
from PySide6.QtWidgets import QWidget, QWizard, QWizardPage
import mtg_proxy_printer.settings
from mtg_proxy_printer.natsort import NaturallySortedSortFilterProxyModel
from mtg_proxy_printer.model.carddb import CardDatabase, Card, MTGSet
from mtg_proxy_printer.model.imagedb import ImageDatabase, CacheContent as ImageCacheContent, ImageKey
from mtg_proxy_printer.ui.common import load_ui_from_file, format_size, WizardBase
from mtg_proxy_printer.units_and_sizes import OptStr
|
︙ | | |
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
-
+
|
source = QPixmap(str(path))
pixmap = source.scaled(
source.width() // scaling_factor, source.height() // scaling_factor,
Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
buffer = QBuffer()
buffer.open(QIODevice.OpenModeFlag.WriteOnly)
pixmap.save(buffer, "PNG", quality=100)
image = buffer.data().toBase64().data().decode()
image = buffer.data().toBase64().toStdString()
card_name = f'<p style="text-align:center">{card_name}</p><br>' if card_name else ""
tooltip_text = f'{card_name}<img src="data:image/png;base64,{image}">'
return tooltip_text
class KnownCardColumns(enum.IntEnum):
Name = 0
|
︙ | | |
Changes to mtg_proxy_printer/ui/central_widget.py.
︙ | | |
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
+
-
-
+
+
|
import functools
import math
import operator
import pathlib
from typing import Union, Type, Optional
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot, QPersistentModelIndex, QItemSelectionModel, \
from PySide6.QtCore import Signal, Slot, QPersistentModelIndex, QItemSelectionModel, \
QModelIndex, QPoint, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QAction, QMenu, QInputDialog, QFileDialog
from PySide6.QtGui import QIcon, QAction
from PySide6.QtWidgets import QWidget, QMenu, QInputDialog, QFileDialog
import mtg_proxy_printer.app_dirs
import mtg_proxy_printer.settings
from mtg_proxy_printer.model.card_list import PageColumns
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.carddb import CardDatabase, Card, CardList, CheckCard, AnyCardType, AnyCardTypeForTypeCheck
from mtg_proxy_printer.model.imagedb import ImageDatabase
|
︙ | | |
Changes to mtg_proxy_printer/ui/common.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
-
-
+
+
+
-
-
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pathlib
import platform
import typing
from PyQt5.QtCore import QFile, QUrl, QObject, QSize, QCoreApplication
from PyQt5.QtWidgets import QLabel, QWizard, QWidget, QGraphicsColorizeEffect, QTextEdit
from PyQt5.QtGui import QIcon
from PySide6.QtCore import QFile, QUrl, QObject, QSize, QCoreApplication
from PySide6.QtWidgets import QLabel, QWizard, QWidget, QGraphicsColorizeEffect, QTextEdit
from PySide6.QtGui import QIcon
# noinspection PyUnresolvedReferences
from PyQt5 import uic
from PySide6.QtUiTools import loadUiType
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
__all__ = [
"RESOURCE_PATH_PREFIX",
|
︙ | | |
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
-
+
-
+
-
+
+
+
|
if not display_text:
display_text = str(path)
label.setText(f"""<a href="{url.path(QUrl.FullyEncoded):s}">{display_text:s}</a>""")
def load_ui_from_file(name: str):
"""
Returns the Ui class type from uic.loadUiType(), loading the ui file with the given name.
Returns the Ui class type as returned by PySide6.QtUiTools.loadUiType(), loading the ui file with the given name.
:param name: Path to the UI file
:return: class implementing the requested Ui
:raises FileNotFoundError: If the given ui file does not exist
"""
file_path = f"{RESOURCE_PATH_PREFIX}/ui/{name}.ui"
if not QFile.exists(file_path):
error_message = f"UI file not found: {file_path}"
logger.error(error_message)
raise FileNotFoundError(error_message)
try:
base_type, _ = uic.loadUiType(file_path, from_imports=True)
base_type, _ = loadUiType(file_path)
except TypeError as e:
raise RuntimeError(f"Ui compilation failed for path {file_path}") from e
return base_type
def load_icon(name: str) -> QIcon:
file_path = f"{RESOURCE_PATH_PREFIX}/icons/{name}"
if not QFile.exists(file_path):
error_message = f"Icon not found: {file_path}"
logger.error(error_message)
|
︙ | | |
Changes to mtg_proxy_printer/ui/compiled_resources.pyi.
︙ | | |
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
-
+
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Declares the interface created by the PyQt5 resource compiler.
Declares the interface created by the PySide6 resource compiler.
This is only used for type hinting.
"""
import typing
qt_version: typing.List[int] = ...
rcc_version: int = ...
|
︙ | | |
Changes to mtg_proxy_printer/ui/deck_import_wizard.py.
︙ | | |
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
+
-
-
+
+
+
|
import math
import pathlib
import re
import typing
import urllib.error
import urllib.parse
from PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal, pyqtProperty as Property, QStringListModel, Qt, \
from PySide6.QtCore import Slot, Signal, Property, QStringListModel, Qt, SIGNAL, \
QItemSelection, QSize, QUrl
from PyQt5.QtGui import QValidator, QIcon, QDesktopServices
from PyQt5.QtWidgets import QWizard, QFileDialog, QMessageBox, QWizardPage, QWidget, QRadioButton
from PySide6.QtGui import QValidator, QIcon, QDesktopServices
from PySide6.QtWidgets import QWizard, QFileDialog, QMessageBox, QWizardPage, QWidget, QRadioButton
from mtg_proxy_printer.units_and_sizes import SectionProxy
import mtg_proxy_printer.settings
from mtg_proxy_printer.decklist_parser import re_parsers, common, csv_parsers
from mtg_proxy_printer.decklist_downloader import IsIdentifyingDeckUrlValidator, AVAILABLE_DOWNLOADERS, \
get_downloader_class, ParserBase
from mtg_proxy_printer.model.carddb import CardDatabase
|
︙ | | |
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
-
+
-
+
-
+
|
ui.deck_list_download_url_line_edit.textChanged.connect(
lambda text: ui.deck_list_download_button.setEnabled(
self.deck_list_url_validator.validate(text)[0] == State.Acceptable))
supported_sites = "\n".join((downloader.APPLICABLE_WEBSITES for downloader in AVAILABLE_DOWNLOADERS.values()))
ui.deck_list_download_url_line_edit.setToolTip(
self.tr("Supported websites:\n{supported_sites}").format(supported_sites=supported_sites))
ui.translate_deck_list_target_language.setModel(language_model)
self.registerField("deck_list*", ui.deck_list, "plainText", ui.deck_list.textChanged)
self.registerField("deck_list*", ui.deck_list)
self.registerField("print-guessing-enable", ui.print_guessing_enable)
self.registerField("print-guessing-prefer-already-downloaded", ui.print_guessing_prefer_already_downloaded)
self.registerField("translate-deck-list-enable", ui.translate_deck_list_enable)
self.registerField("deck-list-downloaded", self, "deck_list_downloader", self.deck_list_downloader_changed)
self.registerField("deck-list-downloaded", self, "deck_list_downloader", "deck_list_downloader_changed(str)")
self.registerField(
"translate-deck-list-target-language", ui.translate_deck_list_target_language,
"currentText", ui.translate_deck_list_target_language.currentTextChanged
"currentText", "currentTextChanged(str)"
)
logger.info(f"Created {self.__class__.__name__} instance.")
@Property(str, notify=deck_list_downloader_changed)
def deck_list_downloader(self):
return self._deck_list_downloader
|
︙ | | |
603
604
605
606
607
608
609
610
611
612
613
614
615
616
|
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
|
+
|
QWizard.WizardButton.FinishButton: "dialog-ok",
QWizard.WizardButton.CancelButton: "dialog-cancel",
}
def __init__(self, card_db: CardDatabase, image_db: ImageDatabase,
language_model: QStringListModel, parent: QWidget = None, flags=Qt.WindowFlags()):
super().__init__(QSize(1000, 600), parent, flags)
self.setDefaultProperty("QPlainTextEdit", "plainText", SIGNAL("textChanged()"))
self.card_db = card_db
self.select_deck_parser_page = SelectDeckParserPage(card_db, image_db, self)
self.load_list_page = LoadListPage(language_model, self)
self.summary_page = SummaryPage(card_db, self)
self.addPage(self.load_list_page)
self.addPage(self.select_deck_parser_page)
self.addPage(self.summary_page)
|
︙ | | |
Changes to mtg_proxy_printer/ui/dialogs.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
-
-
-
+
+
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing
import pathlib
import sys
from PyQt5.QtCore import QFile, pyqtSlot as Slot, QThreadPool, QObject, QEvent, Qt
from PyQt5.QtWidgets import QFileDialog, QWidget, QTextBrowser, QDialogButtonBox, QDialog
from PyQt5.QtGui import QIcon
from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrintDialog, QPrinter
from PySide6.QtCore import QFile, Slot, QThreadPool, QObject, QEvent, Qt
from PySide6.QtWidgets import QFileDialog, QWidget, QTextBrowser, QDialogButtonBox, QDialog
from PySide6.QtGui import QIcon
from PySide6.QtPrintSupport import QPrintPreviewDialog, QPrintDialog, QPrinter
import mtg_proxy_printer.app_dirs
from mtg_proxy_printer.model.carddb import Card, CardDatabase
import mtg_proxy_printer.model.document
import mtg_proxy_printer.model.imagedb
import mtg_proxy_printer.print
import mtg_proxy_printer.settings
|
︙ | | |
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
|
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
|
-
+
|
file_path = self._get_file_path(":/changelog.md", "/../../doc/changelog.md")
self._set_text_browser_with_markdown_file_content(file_path, self.ui.changelog_text_browser)
def _set_text_browser_with_markdown_file_content(self, file_path: str, text_browser: QTextBrowser):
file = QFile(file_path, self)
file.open(QFile.OpenModeFlag.ReadOnly)
try:
content = file.readAll().data().decode("utf-8")
content = file.readAll().toStdString()
finally:
file.close()
text_browser.setMarkdown(content)
class PrintPreviewDialog(QPrintPreviewDialog):
|
︙ | | |
365
366
367
368
369
370
371
372
373
|
365
366
367
368
369
370
371
372
373
|
-
+
|
logger.info(f"User accepted the {self.__class__.__name__}")
action = ActionEditDocumentSettings(self.ui.page_config_container.ui.page_config_widget.page_layout)
self.document.apply(action)
logger.debug("Saving settings in the document done.")
def clear_highlight(self):
"""Clears all GUI widget highlights."""
for item in self.findChildren((QWidget,), options=Qt.FindChildOption.FindChildrenRecursively): # type: QWidget
for item in self.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively): # type: QWidget
item.setGraphicsEffect(None)
|
Changes to mtg_proxy_printer/ui/item_delegates.py.
︙ | | |
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
-
+
+
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing
from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QSortFilterProxyModel
from PyQt5.QtWidgets import QStyledItemDelegate, QWidget, QStyleOptionViewItem, QComboBox
from PySide6.QtCore import QModelIndex, Qt, QAbstractItemModel, QSortFilterProxyModel
from PySide6.QtWidgets import QStyledItemDelegate, QWidget, QStyleOptionViewItem, QComboBox
from mtg_proxy_printer.model.carddb import Card
from mtg_proxy_printer.model.card_list import PageColumns
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
|
︙ | | |
Changes to mtg_proxy_printer/ui/main_window.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
+
-
-
-
-
+
+
+
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pathlib
import typing
from PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal, QStringListModel, QUrl, Qt, QSize
from PyQt5.QtGui import QCloseEvent, QKeySequence, QDesktopServices, QDragEnterEvent, QDropEvent, QPixmap
from PyQt5.QtWidgets import QApplication, QMessageBox, QAction, QWidget, QMainWindow, QDialog
from PySide6.QtCore import Slot, Signal, QStringListModel, QUrl, Qt, QSize
from PySide6.QtGui import QCloseEvent, QKeySequence, QAction, QDesktopServices, QDragEnterEvent, QDropEvent, QPixmap
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget, QMainWindow, QDialog
from PySide6.QtPrintSupport import QPrintDialog
from mtg_proxy_printer.missing_images_manager import MissingImagesManager
from mtg_proxy_printer.card_info_downloader import CardInfoDownloader
from mtg_proxy_printer.model.carddb import CardDatabase, Card, MTGSet
from mtg_proxy_printer.model.imagedb import ImageDatabase
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.document_controller.compact_document import ActionCompactDocument
|
︙ | | |
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
-
|
(self.ui.action_undo, StandardKey.Undo),
(self.ui.action_redo, StandardKey.Redo),
]
for action, shortcut in actions_with_shortcuts:
action.setShortcut(shortcut)
def _setup_central_widget(self):
self.setCentralWidget(self.ui.central_widget)
self.ui.central_widget.set_data(self.document, self.card_database, self.image_db)
def _setup_loading_state_connections(self):
for widget_or_action in self._get_widgets_and_actions_disabled_in_loading_state():
self.loading_state_changed.connect(widget_or_action.setDisabled)
def _setup_undo_redo_actions(self, document: Document):
|
︙ | | |
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
|
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
+
-
+
|
action_str = self.tr(
"printing",
"This is passed as the {action} when asking the user about compacting the document if that can save pages")
if self._ask_user_about_compacting_document(action_str) == StandardButton.Cancel:
return
self.current_dialog = PrintDialog(self.document, self)
self.current_dialog.finished.connect(self.on_dialog_finished)
# Use the QDialog base class open() method, because QPrintDialog.open() performs additional, unwanted actions.
self.missing_images_manager.obtain_missing_images(self.current_dialog.open)
self.missing_images_manager.obtain_missing_images(super(QPrintDialog, self.current_dialog).open)
@Slot()
def on_action_print_preview_triggered(self):
logger.info(f"User views the print preview.")
action_str = self.tr(
"printing",
"This is passed as the {action} when asking the user about compacting the document if that can save pages")
|
︙ | | |
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
|
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
|
-
+
|
self, self.tr("Download required Card data from Scryfall?"),
self.tr(
"This program requires downloading additional card data from Scryfall to operate the card search.\n"
"Download the required data from Scryfall now?\n"
"Without the data, you can only print custom cards by drag&dropping "
"the image files onto the main window."),
StandardButton.Yes | StandardButton.No, StandardButton.Yes) == StandardButton.Yes:
self.ui.action_download_card_data.trigger()
self.card_data_downloader.import_from_api()
@Slot()
def on_action_save_document_triggered(self):
logger.debug("User clicked on Save")
if self.document.save_file_path is None:
logger.debug("No save file path set. Call 'Save as' instead.")
self.ui.action_save_as.trigger()
|
︙ | | |
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
|
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
|
-
+
|
self, self.tr("New card data available"),
self.tr(
"There are %n new printings available on Scryfall. Update the local data now?",
"", estimated_card_count),
StandardButton.Yes | StandardButton.No, StandardButton.Yes
) == StandardButton.Yes:
logger.info("User agreed to update the card data from Scryfall. Performing update")
self.ui.action_download_card_data.trigger()
self.card_data_downloader.import_from_api()
else:
# If the user declines to perform the update now, allow them to perform it later by enabling the action.
self.ui.action_download_card_data.setEnabled(True)
def ask_user_about_application_update_policy(self):
"""Executed on start when the application update policy setting is set to None, the default value."""
name = mtg_proxy_printer.meta_data.PROGRAMNAME
|
︙ | | |
515
516
517
518
519
520
521
522
523
524
|
516
517
518
519
520
521
522
523
524
525
|
-
+
|
regular = mtg_proxy_printer.units_and_sizes.CardSizes.REGULAR
width, height = regular.width.magnitude, regular.height.magnitude
for url in mime_data.urls():
pixmap = QPixmap(url.toLocalFile())
if not pixmap.isNull():
if pixmap.width() != width or pixmap.height() != height:
new_size = QSize(width, height)
pixmap = pixmap.scaled(new_size, transformMode=TransformationMode.SmoothTransformation)
pixmap = pixmap.scaled(new_size, mode=TransformationMode.SmoothTransformation)
result.append(pixmap)
return result
|
Changes to mtg_proxy_printer/ui/page_config_container.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
from PyQt5.QtWidgets import QWidget
from PySide6.QtWidgets import QWidget
from mtg_proxy_printer.ui.common import load_ui_from_file
from mtg_proxy_printer.logger import get_logger
try:
from mtg_proxy_printer.ui.generated.page_config_container import Ui_PageConfigContainer
except ModuleNotFoundError:
|
︙ | | |
Changes to mtg_proxy_printer/ui/page_config_preview_area.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
-
-
+
+
+
-
-
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import enum
from unittest.mock import MagicMock
from PyQt5.QtCore import pyqtSlot as Slot, QPersistentModelIndex
from PyQt5.QtGui import QColorConstants, QPainter, QPixmap
from PySide6.QtCore import Slot, QPersistentModelIndex
from PySide6.QtGui import QColorConstants, QPainter, QPixmap
from PySide6.QtWidgets import QWidget
from mtg_proxy_printer.document_controller.page_actions import ActionNewPage
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard, ActionRemoveCards
from mtg_proxy_printer.model.document_loader import PageLayoutSettings
from mtg_proxy_printer.units_and_sizes import CardSizes, CardSize
from mtg_proxy_printer.model.document_page import PageType
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.carddb import Card, MTGSet
from mtg_proxy_printer.ui.common import load_ui_from_file
from mtg_proxy_printer.logger import get_logger
from PyQt5.QtWidgets import QWidget
try:
from mtg_proxy_printer.ui.generated.page_config_preview_area import Ui_PageConfigPreviewArea
except ModuleNotFoundError:
Ui_PageConfigPreviewArea = load_ui_from_file("page_config_preview_area")
logger = get_logger(__name__)
del get_logger
|
︙ | | |
Changes to mtg_proxy_printer/ui/page_config_widget.py.
︙ | | |
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
-
-
+
+
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import functools
from functools import partial
import math
import typing
from PyQt5.QtCore import pyqtSlot as Slot, Qt, pyqtSignal as Signal
from PyQt5.QtWidgets import QGroupBox, QWidget, QDoubleSpinBox, QCheckBox, QLineEdit
from PySide6.QtCore import Slot, Qt, Signal
from PySide6.QtWidgets import QGroupBox, QWidget, QDoubleSpinBox, QCheckBox, QLineEdit
import mtg_proxy_printer.settings
from mtg_proxy_printer.ui.common import load_ui_from_file, BlockedSignals, highlight_widget
from mtg_proxy_printer.model.document_loader import PageLayoutSettings
from mtg_proxy_printer.units_and_sizes import CardSizes, PageType, unit_registry, ConfigParser, QuantityT
try:
|
︙ | | |
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
-
+
-
+
-
+
+
+
+
-
+
|
for spinbox, _ in self._get_decimal_settings_widgets():
layout_key = spinbox.objectName()
spinbox.valueChanged[float].connect(
partial(self.set_numerical_page_layout_item, page_layout, layout_key, "mm"))
spinbox.valueChanged[float].connect(self.validate_paper_size_settings)
spinbox.valueChanged[float].connect(self.on_page_layout_setting_changed)
spinbox.valueChanged[float].connect(partial(self.page_layout_changed.emit, page_layout))
spinbox.valueChanged[float].connect(lambda: self.page_layout_changed.emit(page_layout))
for checkbox, _ in self._get_boolean_settings_widgets():
layout_key = checkbox.objectName()
checkbox.stateChanged.connect(partial(self.set_boolean_page_layout_item, page_layout, layout_key))
checkbox.stateChanged.connect(partial(self.page_layout_changed.emit, page_layout))
checkbox.stateChanged.connect(lambda: self.page_layout_changed.emit(page_layout))
ui.document_name.textChanged.connect(partial(setattr, page_layout, "document_name"))
ui.document_name.textChanged.connect(partial(self.page_layout_changed.emit, page_layout))
ui.document_name.textChanged.connect(lambda: self.page_layout_changed.emit(page_layout))
return page_layout
@staticmethod
def set_numerical_page_layout_item(page_layout: PageLayoutSettings, layout_key: str, unit: str, value: float):
# Implementation note: This call is placed here, because stuffing it into a lambda defined within a while loop
# somehow uses the wrong references and will set the attribute that was processed last in the loop.
# This method can be used via functools.partial to reduce the signature to (float) -> None,
# which can be connected to the valueChanged[float] signal just fine.
# Also, functools.partial does not exhibit the same issue as the lambda expression shows.
setattr(page_layout, layout_key, value*unit_registry.parse_units(unit))
@staticmethod
def set_boolean_page_layout_item(page_layout: PageLayoutSettings, layout_key: str, value: CheckState):
# Implementation note: This call is placed here, because stuffing it into a lambda defined within a while loop
# somehow uses the wrong references and will set the attribute that was processed last in the loop.
# This method can be used via functools.partial to reduce the signature to (CheckState) -> None,
# which can be connected to the stateChanged signal just fine.
# Also, functools.partial does not exhibit the same issue as the lambda expression shows.
#
# PySide6 maps the QCheckBox check states to proper Python enums, but the stateChanged Qt signal carries raw
# integers. To get the integers for comparison, the lambdas below require accessing the CheckState enum values.
setattr(page_layout, layout_key, value == CheckState.Checked)
setattr(page_layout, layout_key, value == CheckState.Checked.value)
@Slot()
def on_page_layout_setting_changed(self):
"""
Recomputes and updates the page capacity display, whenever any page layout widget changes.
"""
regular_capacity = self.page_layout.compute_page_card_capacity(PageType.REGULAR)
|
︙ | | |
Changes to mtg_proxy_printer/ui/page_renderer.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
-
-
+
+
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import enum
import typing
from functools import partial
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtWidgets import QGraphicsView, QWidget, QAction
from PyQt5.QtGui import QWheelEvent, QKeySequence, QPalette, QResizeEvent
from PySide6.QtCore import Qt, QEvent
from PySide6.QtWidgets import QGraphicsView, QWidget
from PySide6.QtGui import QWheelEvent, QKeySequence, QPalette, QResizeEvent, QAction
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.logger import get_logger
from mtg_proxy_printer.ui.page_scene import RenderMode, PageScene
logger = get_logger(__name__)
del get_logger
|
︙ | | |
Changes to mtg_proxy_printer/ui/page_scene.py.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-
+
-
-
+
+
|
import collections
import enum
import functools
import itertools
import typing
from PyQt5.QtCore import Qt, QSizeF, QPointF, QRectF, pyqtSignal as Signal, QObject, pyqtSlot as Slot, \
from PySide6.QtCore import Qt, QSizeF, QPointF, QRectF, Signal, QObject, Slot, \
QPersistentModelIndex, QModelIndex, QRect, QPoint, QSize
from PyQt5.QtGui import QPen, QColorConstants, QBrush, QColor, QPalette, QFontMetrics, QPixmap, QTransform, QPolygonF
from PyQt5.QtWidgets import QGraphicsItemGroup, QGraphicsItem, QGraphicsPixmapItem, QGraphicsRectItem, \
from PySide6.QtGui import QPen, QColorConstants, QBrush, QColor, QPalette, QFontMetrics, QPixmap, QTransform, QPolygonF
from PySide6.QtWidgets import QGraphicsItemGroup, QGraphicsItem, QGraphicsPixmapItem, QGraphicsRectItem, \
QGraphicsLineItem, QGraphicsSimpleTextItem, QGraphicsScene, QGraphicsPolygonItem
from mtg_proxy_printer.model.card_list import PageColumns
from mtg_proxy_printer.model.carddb import Card, CardCorner
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.document_loader import PageLayoutSettings
from mtg_proxy_printer.units_and_sizes import PageType, unit_registry, RESOLUTION, CardSizes, CardSize, QuantityT
|
︙ | | |
Changes to mtg_proxy_printer/ui/printing_filter_widgets.py.
︙ | | |
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
-
-
+
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
from functools import partial
from typing import List, Tuple
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QGroupBox, QWidget, QCheckBox, QPushButton
from PySide6.QtCore import QUrl
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QGroupBox, QWidget, QCheckBox, QPushButton
from mtg_proxy_printer.units_and_sizes import ConfigParser, SectionProxy
from mtg_proxy_printer.ui.common import highlight_widget
try:
from mtg_proxy_printer.ui.generated.settings_window.format_printing_filter import Ui_FormatPrintingFilter
from mtg_proxy_printer.ui.generated.settings_window.general_printing_filter import Ui_GeneralPrintingFilter
|
︙ | | |
Changes to mtg_proxy_printer/ui/progress_bar.py.
︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
-
-
+
+
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from PyQt5.QtCore import pyqtSlot as Slot, Qt
from PyQt5.QtWidgets import QWidget, QLabel, QProgressBar
from PySide6.QtCore import Slot, Qt
from PySide6.QtWidgets import QWidget, QLabel, QProgressBar
try:
from mtg_proxy_printer.ui.generated.progress_bar import Ui_ProgressBar
except ModuleNotFoundError:
from mtg_proxy_printer.ui.common import load_ui_from_file
Ui_ProgressBar = load_ui_from_file("progress_bar")
|
︙ | | |
Changes to mtg_proxy_printer/ui/settings_window.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
-
-
-
+
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pathlib
import typing
from functools import partial
from PyQt5.QtCore import QStringListModel, pyqtSignal as Signal, Qt, QItemSelectionModel, QEvent, QObject, QTimer
from PyQt5.QtWidgets import QDialogButtonBox, QMessageBox, QWidget, QDialog
from PyQt5.QtGui import QIcon, QStandardItemModel, QResizeEvent
from PySide6.QtCore import QStringListModel, Signal, Qt, QItemSelectionModel, QEvent, QObject, QTimer
from PySide6.QtWidgets import QDialogButtonBox, QMessageBox, QWidget, QDialog
from PySide6.QtGui import QIcon, QStandardItemModel, QResizeEvent
import mtg_proxy_printer.app_dirs
from mtg_proxy_printer.units_and_sizes import ConfigParser
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.document_controller import DocumentAction
from mtg_proxy_printer.document_controller.edit_document_settings import ActionEditDocumentSettings
|
︙ | | |
Changes to mtg_proxy_printer/ui/settings_window_pages.py.
︙ | | |
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
-
-
-
+
+
+
|
import logging
from functools import partial
import pathlib
import typing
from abc import abstractmethod
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot, QUrl, QStandardPaths, QStringListModel, Qt, QThreadPool
from PyQt5.QtGui import QDesktopServices, QStandardItem, QIcon
from PyQt5.QtWidgets import QWidget, QCheckBox, QFileDialog, QMessageBox, QApplication, QLineEdit
from PySide6.QtCore import Signal, Slot, QUrl, QStandardPaths, QStringListModel, Qt, QThreadPool
from PySide6.QtGui import QDesktopServices, QStandardItem, QIcon
from PySide6.QtWidgets import QWidget, QCheckBox, QFileDialog, QMessageBox, QApplication, QLineEdit
import mtg_proxy_printer.app_dirs
import mtg_proxy_printer.settings
from mtg_proxy_printer.printing_filter_updater import PrintingFilterUpdater
from mtg_proxy_printer.logger import get_logger
from mtg_proxy_printer.ui.common import highlight_widget
from mtg_proxy_printer.units_and_sizes import OptStr, ConfigParser
|
︙ | | |
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
-
|
class PageMetadata(typing.NamedTuple):
text: str
icon_name: OptStr
tooltip: OptStr = None
class Page(QWidget):
"""The base class for settings page widgets. Defines the API used by the settings window"""
def display_item(self) -> typing.Sequence[QStandardItem]:
data = self.display_metadata()
item = QStandardItem(data.text)
if data.icon_name:
|
︙ | | |
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
-
+
|
@abstractmethod
def highlight_differing_settings(self, settings: ConfigParser):
"""Highlights GUI widgets with a state different from the given settings"""
pass
def clear_highlight(self):
"""Clears all GUI widget highlights."""
for item in self.findChildren((QWidget,), options=Qt.FindChildOption.FindChildrenRecursively): # type: QWidget
for item in self.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively): # type: QWidget
item.setGraphicsEffect(None)
class DebugSettingsPage(Page):
requested_card_download = Signal(pathlib.Path)
|
︙ | | |
Changes to mtg_proxy_printer/units_and_sizes.py.
︙ | | |
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
-
+
|
from typing_extensions import NotRequired
import pint
try:
from pint.facets.plain.registry import QuantityT, UnitT
except ImportError: # Compatibility with Pint 0.21 for Python 3.8 support
QuantityT = UnitT = typing.Any
from PyQt5.QtCore import QSize
from PySide6.QtCore import QSize
def _setup_units() -> typing.Tuple[pint.UnitRegistry, QuantityT]:
registry = pint.UnitRegistry()
resolution = registry.parse_expression("300dots/inch")
print_context = pint.Context("print")
print_context.add_transformation("[length]", "[printing_unit]", lambda _, x: x*RESOLUTION)
|
︙ | | |
Changes to mtg_proxy_printer/update_checker.py.
︙ | | |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
-
+
|
import re
import socket
import typing
import urllib.parse
import urllib.error
import ijson
from PyQt5.QtCore import QObject, pyqtSignal as Signal, QThreadPool
from PySide6.QtCore import QObject, Signal, QThreadPool
from mtg_proxy_printer.argument_parser import Namespace
import mtg_proxy_printer.meta_data
from mtg_proxy_printer import settings
from mtg_proxy_printer.model.carddb import CardDatabase
from mtg_proxy_printer.card_info_downloader import CardInfoDatabaseImportWorker, CardInfoWorkerBase
from mtg_proxy_printer.natsort import natural_sorted, str_less_than
|
︙ | | |
Changes to pyproject.toml.
︙ | | |
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
-
+
|
]
# Minimum required ijson version is 3.1 for the use_float parameter.
# ijson adds full compatibility with Python 3.11 in version 3.2 (3.1 is really slow on Py3.11).
# and Python 3.12 support in 3.2.1. Require newer versions on those Python versions to avoid
# falling back to the slow pure Python backend.
dependencies = [
"platformdirs >= 2.6.0",
"PyQt5",
"PySide6_Essentials >= 6.7.0",
"ijson >= 3.1.0; python_version < '3.11'",
"ijson >= 3.2.0; python_version >= '3.11'",
"ijson >= 3.2.1; python_version >= '3.12'",
"pint < 0.22; python_version < '3.9'", # 0.22 dropped Py 3.8 support, thus Win7 support
"pint >= 0.22; python_version >= '3.9'", # Requires 0.22 for the QuantityT and UnitT types
"delegateto == 1.5",
"PyHamcrest >= 2",
|
︙ | | |
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
-
-
-
-
-
+
-
-
|
[project.optional-dependencies]
dev = [
"pytest",
"pytest-cov",
"pytest-timeout",
"pytest-qt >= 2.0",
"tox >= 4.0",
"PySide2; platform_system == 'Windows' and python_version < '3.12'", # Used for lrelease: Compiling translations
"PySide6_Essentials; platform_system == 'Windows' and python_version >= '3.12'", # Used for lrelease: Compiling translations
"pillow; platform_system == 'Windows'", # Used to convert the app icon to .ico
]
package = [
"build",
# cx_Freeze 7.2 seems to somehow break loading the compiled Qt resources, but only for PyQt5.
# Limit to 7.1 for now, until a fix is found
"cx_Freeze >= 6.11.1, < 7.2; platform.python_implementation == 'CPython'",
"cx_Freeze >= 6.11.1; platform.python_implementation == 'CPython'",
"PySide2; platform_system == 'Windows' and python_version < '3.12'", # Used for lrelease: Compiling translations
"PySide6_Essentials; platform_system == 'Windows' and python_version >= '3.12'", # Used for lrelease: Compiling translations
"pillow; platform_system == 'Windows'", # Used to convert the app icon to .ico
]
[project.urls]
Homepage = "https://chiselapp.com/user/luziferius/repository/MTGProxyPrinter/index"
"Bug tracker" = "https://chiselapp.com/user/luziferius/repository/MTGProxyPrinter/ticket"
|
︙ | | |
102
103
104
105
106
107
108
109
110
111
112
113
|
96
97
98
99
100
101
102
103
104
105
106
107
|
-
+
|
version = {attr= "mtg_proxy_printer.meta_data.__version__"}
[build-system]
requires = [
"setuptools >=61",
"wheel",
"PyQt5",
"PySide6_Essentials >= 6.7.0",
"build",
"tox >= 4.0",
]
build-backend = "setuptools.build_meta"
|
Changes to run_tests.bat.
1
2
3
4
5
6
7
8
9
10
|
1
2
3
4
5
6
7
8
9
10
|
-
-
+
+
|
:: Runs the unit tests
:: Create or activate the build environment
IF EXIST "venv" (
call venv\Scripts\activate.bat
IF EXIST "venv-PySide6" (
call venv-PySide6\Scripts\activate.bat
) ELSE (
call create_development_environment.bat
)
tox run
|
Changes to run_tests.sh.
1
2
3
4
5
|
1
2
3
4
5
|
-
+
|
#!/bin/bash
source venv/bin/activate
source venv-PySide6/bin/activate
tox run
deactivate
|
Changes to scripts/clean_windows_build.bat.
︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
-
-
-
+
-
+
-
-
+
-
-
-
+
+
-
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
+
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
+
-
+
-
-
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
+
-
-
-
-
-
-
|
:: but WITHOUT ANY WARRANTY; without even the implied warranty of
:: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
:: GNU General Public License for more details.
::
:: You should have received a copy of the GNU General Public License
:: along with this program. If not, see <http://www.gnu.org/licenses/>.
:: Cleanup various items that bloat the application bundle. Mostly related to unused PyQt5 and Qt5 components
if "%1%"=="" (
pushd build\exe*
) else (
pushd "%1"
)
rmdir /S /Q PyQt5.uic.widget-plugins
pushd lib
pushd lib
pushd PySide6
del ijson\backends\python*.dll
:: Don't need the executables, like Qt6 Designer, etc.
pushd PyQt5
:: All DLLs here are also in Qt5\bin\
del *.dll
:: Delete all typing stubs
del *.exe *.pyi
del QtRemoteObjects.pyd QtSerialPort.pyd QtSensors.pyd QtNetwork.pyd QtXml.pyd QtXmlPatterns.pyd pyrcc.pyd
:: Remove unused components. Each consists of a pair of QtComponent.pyd and Qt6Component.dll
del Q*tSerialPort* Qt*DBus* Qt*Designer* Qt*JsonRpc* Qt*Labs* Qt*LanguageServer* Qt*Network*
del Qt*Qml* Qt*Quick* Qt*RemoteObjects* Qt*Sensors* Qt*Sql* Qt*Test* Qt*WebChannel*
:: The sip bindings aren't used at runtime
rmdir /S /Q bindings
pushd Qt5
del Qt*Bluetooth* Qt*Charts* Qt*Concurrent* Qt*DataVisualization* Qt*Graphs* Qt*HttpServer*
del Qt*Nfc* Qt*Positioning* Qt*Scxml* Qt*SerialBus* Qt*SerialPort* Qt*ShaderTools*
del Qt*StateMachine* Qt*Test* Qt*TextToSpeech* Qt*Web* Qt*3D*
del Qt*Help* Qt*Multimedia* Qt*Xml*
:: Unused Qsci
:: Unused audio/video codec DLLs
rmdir /S /Q qsci
del avformat-*.dll avutil-*.dll swresample-*.dll swscale-*.dll
:: Unused Qml bindings
rmdir /S /Q qml
:: Unused OpenGL bindings. The Qt6OpenGL DLLs are required and thus not removed
del QtOpenGL.pyd QtOpenGLWidgets.pyd opengl32sw.dll
:: Unused DLLs
pushd bin
del d3dcompiler_47.dll libEGL.dll libGLESv2.dll opengl32sw.dll Qt5Bluetooth.dll Qt5DBus.dll Qt5Designer.dll Qt5Help.dll
del Qt5Location.dll Qt5Multimedia.dll Qt5MultimediaWidgets.dll Qt5Network.dll Qt5Nfc.dll Qt5OpenGL.dll Qt5Positioning.dll
del Qt5PositioningQuick.dll Qt5Qml.dll Qt5QmlModels.dll Qt5QmlWorkerScript.dll Qt5Quick.dll Qt5Quick3D.dll
del Qt5Quick3DAssetImport.dll Qt5Quick3DRender.dll Qt5Quick3DRuntimeRender.dll Qt5Quick3DUtils.dll Qt5QuickControls2.dll
del Qt5QuickParticles.dll Qt5QuickShapes.dll Qt5QuickTemplates2.dll Qt5QuickTest.dll Qt5QuickWidgets.dll Qt5RemoteObjects.dll
del Qt5Sensors.dll Qt5SerialPort.dll Qt5Sql.dll Qt5Test.dll Qt5WebChannel.dll
del Qt5WebSockets.dll Qt5WebView.dll Qt5XmlPatterns.dll
del libcrypto-1_1-x64.dll libssl-1_1-x64.dll
popd
:: Unused plugins
pushd plugins
pushd translations
:: Remove duplicated Qt5 base DLLs
:: Remove translations for unused/removed components
FOR %%G IN ( printsupport platforms imageformats styles
) DO del %%G\Qt5*.dll
del assistant* designer* linguist* qtdeclarative*
:: leave translations
FOR %%G IN (
assetimporters audio geometryloaders geoservices mediaservice playlistformats
position renderers sceneparsers sensorgestures sensors sqldrivers texttospeech webview
) DO rmdir /S /Q %%G
popd
pushd plugins
del /Q /S tls
:: Unused translations (of unused modules)
:: leave plugins
pushd translations
del qtxmlpatterns_*.qm
del qtconnectivity_*.qm
del qtdeclarative_*.qm
del qtlocation_*.qm
del qtmultimedia_*.qm
del qtquickcontrols_*.qm
del qtquickcontrols2_*.qm
del qtserialport_*.qm
del qtwebsockets_*.qm
popd
:: leave Qt5
:: leave PySide6
popd
:: Unused extension modules
del *.pyi
del shiboken6\shiboken6*.lib
del email\architecture.rst
del QAxContainer.pyd QtBluetooth.pyd QtDBus.pyd QtDesigner.pyd QtHelp.pyd QtLocation.pyd QtMultimedia.pyd QtMultimediaWidgets.pyd
del QtOpenGL.pyd QtNfc.pyd QtPositioning.pyd QtQml.pyd QtQuick.pyd QtQuick3D.pyd QtQuickWidgets.pyd
del QtRemoteObjects.pydQtSensors.pydQtSerialPort.pyd QtSql.pyd QtTest.pyd QtTextToSpeech.pyd QtWebChannel.pyd
del QtWebSockets.pyd _QOpenGLFunctions_2_0.pyd _QOpenGLFunctions_2_1.pyd _QOpenGLFunctions_4_1_Core.pyd
:: leave PyQt5
popd
:: leave lib
popd
::leave build directory
popd
|
Changes to scripts/compile_resources.py.
︙ | | |
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
-
+
-
|
def split_iterable(iterable: typing.Iterable[T], chunk_size: int, /) -> typing.List[typing.Tuple[T, ...]]:
"""Split the given iterable into chunks of size chunk_size. Does not add padding values to the last item."""
iterable = iter(iterable)
return list(iter(lambda: tuple(itertools.islice(iterable, chunk_size)), ()))
def compile():
command = ("pyrcc5", "-compress", "9", str(SOURCES_PATH)) # noqa # "pyrcc5" is a program name, not a typo
command = ("pyside6-rcc", "--compress", "9", "--generator", "python", str(SOURCES_PATH))
compiled = subprocess.check_output(command, universal_newlines=True) # type: str
# The resource compiler outputs > 15000 lines with extremely low line length.
# Reduce the file size by removing a good percentage of those line breaks
blocks = compiled.split("\\\n")
chunks = split_iterable(blocks, 7)
joined_chunks = ("".join(items) for items in chunks)
compiled = "\\\n".join(joined_chunks)
TARGET_PATH.write_text(compiled, "utf-8")
def clean():
TARGET_PATH.unlink(missing_ok=True)
def main():
args = parse_args()
|
︙ | | |
Changes to scripts/compile_ui_files.py.
︙ | | |
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
-
+
-
-
|
like Python wheels and application bundles created via cx_Freeze.
- Creation of type hinting stubs with suffix ".pyi". These are used during development,
to provide type hinting and autocompletion for the Ui classes defined by the UI files.
"""
import argparse
import ast
import io
import itertools
import textwrap
from pathlib import Path
import shutil
import subprocess
from typing import Tuple, NamedTuple, TypeVar, Iterable, Union, Type, List, Any, Dict, Set
import PyQt5.uic
SOURCE_ROOT = Path(__file__).parent.parent # Checkout root directory
MAIN_PACKAGE = SOURCE_ROOT / "mtg_proxy_printer"
UI_SOURCE_PATH = MAIN_PACKAGE / "resources/ui" # UI files live here
TARGET_PATH = MAIN_PACKAGE / "ui/generated" # Package containing generated modules/type hinting stubs
T = TypeVar("T")
ClassRegistry = Dict[str, ast.ImportFrom]
UsedClasses = Set[str]
|
︙ | | |
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
+
+
+
+
+
+
-
-
+
+
-
-
+
-
-
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
+
-
+
|
args = parser.parse_args()
return args
def type_filter(any_: Iterable[Any], types: Union[Type[T], Tuple[Type[T], ...]]) -> Iterable[T]:
return filter(lambda x: isinstance(x, types), any_)
def create_python_package(location: Path, /):
"""Creates an empty Python package at the given Path"""
location.mkdir(parents=True, exist_ok=True)
(location/"__init__.py").touch(exist_ok=True)
def compile_ui_files(args: Namespace, target_path: Path = TARGET_PATH, source_path: Path = UI_SOURCE_PATH):
"""
Compiles all UI files found in source_path to Python types, storing results in target_path.
Recursively finds UI files under source_path, replicates the found directory tree as a Python package hierarchy and
populates it with the compiled Ui types.
"""
if args.purge_existing and target_path.is_dir():
shutil.rmtree(target_path)
source_path = source_path.resolve()
create_python_package(target_path)
for ui_file in source_path.rglob("*.ui"):
target_path.mkdir(exist_ok=True)
compiled = compile_ui_file(ui_file)
def map_to_output(directory, file_name):
dir_path = Path(directory).relative_to(source_path)
parent_dir = (target_path/ui_file.relative_to(source_path)).parent
return target_path/dir_path, file_name
import functools
PyQt5.uic.open = functools.partial(open, encoding="utf-8")
PyQt5.uic.compileUiDir(str(source_path), recurse=True, map=map_to_output)
create_python_package(target_path)
create_python_package(parent_dir)
(parent_dir/f"{ui_file.stem}.py").write_text(compiled, "utf-8")
def create_python_package(target_dir: Path):
"""
Creates an empty __init__.py file in target_dir and each subdirectory, recursively.
This marks these directories as proper Python packages.
"""
(target_dir/"__init__.py").touch(exist_ok=True)
for entry in target_dir.rglob("*"):
if entry.is_dir():
(entry/"__init__.py").touch(exist_ok=True)
def create_ui_type_stubs(args: Namespace, target_path: Path = TARGET_PATH, source_path: Path = UI_SOURCE_PATH):
"""
Creates type hinting stubs for all UI files found in source_path, storing results in target_path.
Recursively finds UI files under source_path, replicates the found directory tree as a Python package hierarchy and
populates it with the created type hints.
"""
if args.purge_existing and target_path.is_dir():
shutil.rmtree(target_path)
class_registry = build_class_registry(MAIN_PACKAGE)
create_python_package(target_path)
for ui_file in source_path.rglob("*.ui"):
compiled = compile_ui_file(ui_file)
stub = generate_stub(compiled, ui_file, class_registry)
parent_dir = (target_path/ui_file.relative_to(source_path)).parent
parent_dir.mkdir(exist_ok=True)
create_python_package(parent_dir)
(parent_dir/f"{ui_file.stem}.pyi").write_text(stub, "utf-8")
create_python_package(target_path)
def build_class_registry(package_path: Path) -> ClassRegistry:
"""Scan the source tree for classes and build a dict from class name to import path"""
result: ClassRegistry = {}
for py_file in package_path.rglob("*.py"):
module_path = ".".join((py_file.parent.relative_to(package_path.parent) / py_file.stem).parts)
root_node = ast.parse(py_file.read_text("utf-8"), py_file)
for class_def in type_filter(root_node.body, ast.ClassDef):
result[class_def.name] = ast.ImportFrom(module_path, [ast.alias(class_def.name)])
return result
def compile_ui_file(path: Path) -> str:
buffer = io.StringIO()
try:
PyQt5.uic.compileUi(path, buffer, from_imports=True)
command = ("pyside6-uic", "--generator", "python", str(path))
except Exception as e:
raise RuntimeError(f"Compilation failed for file {path}") from e
return buffer.getvalue()
return subprocess.check_output(command, encoding="utf-8")
def generate_stub(compiled_ui: str, ui_file: Path, class_registry: ClassRegistry) -> str:
root_node = ast.parse(compiled_ui)
header = f"# Automatically generated type hinting stub for '{ui_file.name}'. Do not modify."
# Keep all imports unmodified
imports = "import typing\n\n"
|
︙ | | |
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
|
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
-
+
-
-
-
-
-
-
-
-
-
|
return f"class {class_root.name}({base_classes}):"
def get_assignments(function_body: List[ast.stmt]) -> List[Assignment]:
return [
Assignment(
assignment.targets[0].attr,
get_assignment_type(assignment)
assignment.value.func.id
)
for assignment
in type_filter(function_body, ast.Assign)
if hasattr(assignment.targets[0], "attr") # Filter out local variables
]
def get_assignment_type(assignment: ast.Assign):
func = assignment.value.func
if isinstance(func, ast.Attribute):
return f"{func.value.id}.{func.attr}" # Qualified name: module.ClassName()
elif isinstance(func, ast.Name):
return func.id
raise NotImplementedError("Unknown assignment type")
def get_function_stub(function_body: ast.FunctionDef, found_class_uses: UsedClasses):
for index, arg in enumerate(function_body.args.args):
if arg.arg == "self":
continue
found_class_uses.add(arg.arg)
arg.annotation = ast.Constant(arg.arg)
|
︙ | | |
Changes to scripts/update_translations.py.
︙ | | |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
-
-
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Management script for application translations
"""
import argparse
import itertools
import pathlib
import re
import subprocess
from typing import Callable, NamedTuple
# Mapping between source locales, as provided by Crowdin, and the target, as expected/loaded by Qt.
# TODO: Investigate, how systems behave in locales requiring the country as disambiguation, like en or zh.
LOCALES = {
"de-DE": "de",
# "en-GB": "en_GB",
"en-US": "en_US",
"es-ES": "es",
"fr-FR": "fr",
"it-IT": "it",
"ja-JP": "ja",
"ko-KR": "ko",
"pt-PT": "pt",
|
︙ | | |
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
+
|
# Fetch the name of the sources .ts file from crowdin.yml.
# (Since Python does not come with a YAML parser, use a simple RE for data extraction)
re.search(
r'"source":\s*"(?P<path>.+)",',
crowdin_yml_path.read_text("utf-8")
)["path"]
)
class Namespace(NamedTuple):
"""Mock namespace for type hinting"""
command: Callable[["Namespace"], None]
def parse_args() -> Namespace:
|
︙ | | |
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
-
-
-
-
-
-
-
-
-
-
-
|
subprocess.check_output("crowdin")
except FileNotFoundError as e:
raise RuntimeError("The required Crowdin CLI client is not installed in the PATH, exiting.") from e
def register_new_raw_strings():
TRANSLATIONS_DIR.mkdir(parents=True, exist_ok=True)
# PyQt5
package = pathlib.Path("mtg_proxy_printer")
files = list(itertools.chain(package.rglob("*.py"), package.rglob("*.ui")))
subprocess.call([
"pylupdate5",
"-noobsolete", "-verbose",
*files,
"-ts", SOURCES_PATH
])
''' PySide6
subprocess.call([
"pyside6-lupdate",
"-source-language", "en_US",
"-recursive", "-no-obsolete",
"-extensions", "py,ui",
"mtg_proxy_printer",
"-ts", SOURCES_PATH
])
'''
def upload_raw_strings(args: Namespace):
"""Updates the sources .ts from code, then uploads it to the Crowdin API"""
register_new_raw_strings()
verify_crowdin_cli_present()
subprocess.call([
|
︙ | | |
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
-
-
+
-
+
|
try:
subprocess.check_output(["lrelease", "-version"])
except FileNotFoundError:
print("lrelease not found on PATH. Falling back to the executable supplied by PySide2.")
import sys
exe = pathlib.Path(sys.executable)
venv = exe.parent.parent
lrelease5 = venv / "Lib" / "site-packages" / "PySide2" / "lrelease.exe"
lrelease6 = venv / "Lib" / "site-packages" / "PySide6" / "lrelease.exe"
if not lrelease5.is_file() and not lrelease6.is_file():
if not lrelease6.is_file():
raise RuntimeError("No fallback lrelease executable found")
return lrelease5 if lrelease5.is_file() else lrelease6
return lrelease6
else:
return "lrelease"
def compile_translations(args: Namespace):
"""Compiles .ts files into importable, binary translation files with .qm suffix."""
lrelease = get_lrelease()
|
︙ | | |
Changes to setup_cx_freeze.py.
︙ | | |
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
+
+
-
+
+
+
-
-
-
+
-
-
-
+
+
-
-
-
-
+
+
+
-
-
+
-
-
-
-
+
+
+
-
-
+
-
-
-
+
-
-
+
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
-
|
base = "Win32GUI" if sys.platform == "win32" else None
excludes = [
f"{main_package}.resources", # Do not include the raw resources as individual files
"distutils",
"ijson.benchmark", # Ignore the benchmark script added after ijson 3.2.3
"importlib_metadata",
"lib2to3",
"pep517",
"pytest",
"pint.testsuite", # Ignore the internal test suite
"pydoc_data",
"pytest",
"sqlite3.test", # Ignore the internal test suite
"tkinter",
"toml",
"sqlite3.test", # Ignore the internal test suite
"pint.testsuite", # Ignore the internal test suite
"ijson.benchmark", # Ignore the benchmark script added after ijson 3.2.3
# Empty package with readme and download scripts
"importlib_metadata",
# All unused PyQt components
"ctypes.test",
# Unused PySide6 components
"PyQt5.QtXmlPatterns",
"PyQt5.QtNfc",
"PyQt5.QtQml",
"PyQt5.QtSql",
"PySide6.glue",
"PySide6.include",
"PySide6.metatypes",
"PyQt5.Qt3DAnimation",
"PyQt5.Qt3DCore",
"PySide6.plugins.assetimporters",
"PyQt5.Qt3DExtras",
"PyQt5.Qt3DInput",
"PyQt5.Qt3DLogic",
"PyQt5.Qt3DRender",
"PySide6.plugins.canbus",
"PySide6.plugins.designer",
"PySide6.plugins.geometryloaders",
"PyQt5.QtBluetooth",
"PyQt5.QtChart",
"PySide6.plugins.geoservices",
"PyQt5.QtDataVisualisation",
"PyQt5.QtLocation",
"PyQt5.QtMultimedia",
"PySide6.plugins.multimedia",
"PyQt5.QtMultimediaWidgets",
"PyQt5.QtNetwork",
"PySide6.plugins.networkinformation",
"PyQt5.QtNetworkAuth",
"PyQt5.QtOpenGL",
"PyQt5.QtPositioning",
"PyQt5.QtPurchasing",
"PyQt5.QtQuick",
"PySide6.plugins.position",
"PySide6.plugins.qmltooling",
"PySide6.plugins.scxmldatamodel",
"PyQt5.QtQuickWidgets",
"PyQt5.QtRemoteObjects",
"PyQt5.QtSensors",
"PyQt5.QtSerialPort",
"PyQt5.QtTest",
"PyQt5.QtWebChannel",
"PyQt5.QtWebEngine",
"PyQt5.QtWebEngineCore",
"PyQt5.QtWebEngineWidgets",
"PyQt5.QtWebKit",
"PySide6.plugins.sensors",
"PySide6.plugins.sqldrivers", # Use Python native sqlite3 module instead
"PySide6.plugins.tls",
"PySide6.qml",
"PySide6.QtAsyncio",
"PySide6.resources",
"PySide6.scripts",
"PySide6.support",
"PySide6.translations.qtwebengine_locales",
"PyQt5.QtWebKitWidgets",
"PyQt5.QtWebSockets",
"PySide6.typesystems",
"PyQt5.uic.port_v2",
]
if sys.platform == "win32":
excludes += [
"platformdirs.android",
"platformdirs.macos",
"platformdirs.unix",
|
︙ | | |
Changes to tests/conftest.py.
︙ | | |
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
+
|
fixtures defined here are available in all test modules.
"""
import itertools
import sqlite3
import unittest.mock
from pathlib import Path
from PyQt5.QtGui import QColorConstants, QPixmap
from PySide6.QtGui import QColorConstants, QPixmap
import pytest
import mtg_proxy_printer.sqlite_helpers
import mtg_proxy_printer.settings
from mtg_proxy_printer.printing_filter_updater import PrintingFilterUpdater
from mtg_proxy_printer.model.carddb import CardDatabase
from mtg_proxy_printer.model.document import Document
|
︙ | | |
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
-
+
-
-
-
|
":memory:", "document-v6", CardDatabase.MIN_SUPPORTED_SQLITE_VERSION, check_same_thread=False)
if request.param:
db.execute("PRAGMA reverse_unordered_selects = TRUE")
return db
@pytest.fixture
def image_db(tmp_path: Path):
def image_db(qtbot, tmp_path: Path):
image_db = ImageDatabase(tmp_path)
regular_width, regular_height = image_db.blank_image.width(), image_db.blank_image.height()
for scryfall_id, is_front in itertools.product(
["0000579f-7b35-4ed3-b44c-db2a538066fe", "b3b87bfc-f97f-4734-94f6-e3e2f335fc4d"], [True, False]):
# Regular card images
key = ImageKey(scryfall_id, is_front, True)
image_db.loaded_images[key] = image_db.blank_image.copy(0, 0, regular_width, regular_height)
image_db.images_on_disk.add(key)
for scryfall_id in ["650722b4-d72b-4745-a1a5-00a34836282b"]:
# Oversized card images
key = ImageKey(scryfall_id, True, True)
image_db.loaded_images[key] = image_db.blank_image.scaled(regular_height, regular_width*2)
image_db.images_on_disk.add(key)
yield image_db
image_db.__dict__.clear()
@pytest.fixture
def document(qtbot, card_db: CardDatabase, image_db: ImageDatabase) -> Document:
fill_card_database_with_json_cards(qtbot, card_db, [
"regular_english_card", "oversized_card", "english_double_faced_card"])
document = Document(card_db, image_db)
document.loader.db = card_db.db
yield document
document.__dict__.clear()
@pytest.fixture
def document_light(qtbot) -> Document:
mock_card_db = unittest.mock.NonCallableMagicMock()
mock_image_db = unittest.mock.NonCallableMagicMock(spec=ImageDatabase)
mock_image_db.blank_image = QPixmap(CardSizes.REGULAR.as_qsize_px())
mock_image_db.blank_image.fill(QColorConstants.Transparent)
mock_card_db.db = mtg_proxy_printer.sqlite_helpers.create_in_memory_database(
"carddb", CardDatabase.MIN_SUPPORTED_SQLITE_VERSION, check_same_thread=False)
document = Document(mock_card_db, mock_image_db)
document.loader.db = mock_card_db.db
yield document
document.__dict__.clear()
|
Changes to tests/document_controller/test_action_move_cards.py.
︙ | | |
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
+
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
import pytest
from hamcrest import *
from PyQt5.QtCore import QModelIndex
from PySide6.QtCore import QModelIndex
from mtg_proxy_printer.model.document_page import PageType
from mtg_proxy_printer.document_controller import IllegalStateError
from mtg_proxy_printer.document_controller.page_actions import ActionNewPage
from mtg_proxy_printer.document_controller.move_cards import ActionMoveCards
from .helpers import card_container_with, append_new_card_in_page, card_container_with_name
|
︙ | | |
Changes to tests/document_controller/test_action_remove_page.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
from hamcrest import *
from PyQt5.QtCore import QModelIndex
from PySide6.QtCore import QModelIndex
from mtg_proxy_printer.model.document_page import Page
from mtg_proxy_printer.document_controller import IllegalStateError
from mtg_proxy_printer.document_controller.page_actions import ActionRemovePage
from .helpers import append_new_pages, append_new_card_in_page, card_container_with, verify_page_index_cache_is_valid
|
︙ | | |
Changes to tests/test___main__.py.
︙ | | |
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
-
+
|
@pytest.fixture
def main_mocks():
with patch("mtg_proxy_printer.__main__.mtg_proxy_printer.logger.configure_root_logger") as configure_root_logger, \
patch.multiple(
"mtg_proxy_printer.__main__",
_app=DEFAULT, Application=DEFAULT, handle_ssl_certificates=DEFAULT,
parse_args=DEFAULT, QTimer=DEFAULT, logger=DEFAULT, QApplication=DEFAULT) as mocks:
parse_args=DEFAULT, QTimer=DEFAULT, logger=DEFAULT) as mocks:
mocks["configure_root_logger"] = configure_root_logger
yield mocks
mocks.clear()
def test_main_calls_handle_ssl_certificates(main_mocks):
mtg_proxy_printer.__main__.main()
|
︙ | | |
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
-
+
|
def test_main_configures_logger(main_mocks):
mtg_proxy_printer.__main__.main()
main_mocks["configure_root_logger"].assert_called_once()
def test_main_calls_exec_on_application_instance(main_mocks):
mtg_proxy_printer.__main__.main()
main_mocks["Application"]().exec_.assert_called_once()
main_mocks["Application"]().exec.assert_called_once()
def test_enqueues_startup_tasks_on_regular_launch(main_mocks):
main_mocks["parse_args"].return_value = Namespace(test_exit_on_launch=False)
mtg_proxy_printer.__main__.main()
app = main_mocks["Application"]()
app.enqueue_startup_tasks.assert_called_once()
|
︙ | | |
Changes to tests/test_card_list.py.
︙ | | |
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
-
+
|
from collections import Counter
import typing
from hamcrest import *
import pytest
from pytestqt.qtbot import QtBot
from PyQt5.QtCore import QItemSelectionModel
from PySide6.QtCore import QItemSelectionModel
from mtg_proxy_printer.model.carddb import CardDatabase, CardIdentificationData
from mtg_proxy_printer.model.card_list import CardListModel
from tests.helpers import fill_card_database_with_json_cards
OVERSIZED_ID = "650722b4-d72b-4745-a1a5-00a34836282b"
|
︙ | | |
Changes to tests/test_carddb.py.
︙ | | |
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
-
+
-
|
"regular_english_card",
"english_double_faced_card",
"english_double_faced_art_series_card",
"Flowerfoot_Swordmaster_card",
"Flowerfoot_Swordmaster_token",
],
)
yield card_db
return card_db
card_db.__dict__.clear()
def generate_test_cases_for_test_translate_card_name():
"""Yields tuples with card data, target language and expected result."""
# Same-language identity translation
yield CardIdentificationData("en", "Forest"), "en", "Forest"
yield CardIdentificationData("de", "Wald"), "de", "Wald"
|
︙ | | |
Changes to tests/test_check_card_rendering.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from hamcrest import *
from pytestqt.qtbot import QtBot
from PyQt5.QtGui import QPixmap, QColorConstants
from PySide6.QtGui import QPixmap, QColorConstants
from mtg_proxy_printer.model.carddb import Card, CheckCard, MTGSet
from mtg_proxy_printer.units_and_sizes import CardSizes
@pytest.fixture
def blank_image(qtbot) -> QPixmap:
|
︙ | | |
Changes to tests/test_document.py.
︙ | | |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
-
+
+
+
|
import copy
import dataclasses
import pathlib
import typing
import unittest.mock
import textwrap
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from hamcrest import *
from hamcrest import contains_exactly
import pytest
from pytestqt.qtbot import QtBot
from mtg_proxy_printer.model.card_list import PageColumns
|
︙ | | |
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
|
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
|
-
+
-
|
custom_layout = PageLayoutSettings(
page_height=300*mm, page_width=200*mm,
margin_top=20*mm, margin_bottom=19*mm, margin_left=18*mm, margin_right=17*mm,
row_spacing=3*mm, column_spacing=2*mm, card_bleed=1*mm,
draw_cut_markers=True, draw_sharp_corners=False,
)
document.apply(ActionEditDocumentSettings(custom_layout))
yield document
return document
document.__dict__.clear()
def test_document_reset_clears_modified_page_layout(qtbot: QtBot, document_custom_layout: Document):
default_layout = PageLayoutSettings.create_from_settings()
assert_that(
document_custom_layout,
has_property("page_layout", not_(equal_to(default_layout)))
|
︙ | | |
Changes to tests/test_image_db.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
-
+
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
from PyQt5.QtCore import QBuffer, QIODevice
from PyQt5.QtGui import QPixmap
from PySide6.QtCore import QBuffer, QIODevice
from PySide6.QtGui import QPixmap
from hamcrest import *
from pytestqt.qtbot import QtBot
from mtg_proxy_printer.model.imagedb import ImageDatabase, ImageKey
def qpixmap_to_bytes_io(pixmap: QPixmap) -> io.BytesIO:
|
︙ | | |
51
52
53
54
55
56
57
58
|
51
52
53
54
55
56
57
|
-
|
image_db.delete_disk_cache_entries([keys[0]])
assert_that((image_db.db_path / keys[0].format_relative_path()).is_file(), is_(False))
assert_that((image_db.db_path / keys[1].format_relative_path()).is_file(), is_(True))
assert_that((image_db.db_path / keys[0].format_relative_path()).parent.is_dir(), is_(True))
image_db.delete_disk_cache_entries([keys[1]])
assert_that((image_db.db_path / keys[1].format_relative_path()).is_file(), is_(False))
assert_that((image_db.db_path / keys[0].format_relative_path()).parent.is_dir(), is_(False))
|
Changes to tests/test_known_card_image_model.py.
︙ | | |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
+
|
"""
Tests the KnownCardImageModel used internally by the CacheCleanupWizard.
"""
import pathlib
import typing
from PyQt5.QtCore import Qt
from PySide6.QtCore import Qt
import pytest
from hamcrest import *
from mtg_proxy_printer.ui.cache_cleanup_wizard import KnownCardImageModel, KnownCardColumns
from mtg_proxy_printer.model.carddb import CardDatabase
from mtg_proxy_printer.model.imagedb import ImageDatabase
|
︙ | | |
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
-
|
front_image = image_db.db_path/"lowres_front"/"b3"/"b3b87bfc-f97f-4734-94f6-e3e2f335fc4d.png"
back_image = image_db.db_path/"lowres_back"/"b3"/"b3b87bfc-f97f-4734-94f6-e3e2f335fc4d.png"
front_image.parent.mkdir(parents=True)
back_image.parent.mkdir(parents=True)
image_db.blank_image.save(str(front_image), "PNG")
image_db.blank_image.save(str(back_image), "PNG")
yield Environment(card_db, image_db, front_image, back_image)
image_db.__dict__.clear()
@pytest.mark.parametrize("is_hidden", [True, False])
@pytest.mark.parametrize("is_front", [True, False])
def test_add_row_identifies_low_resolution_images(environment: Environment, is_front: bool, is_hidden: bool):
model = KnownCardImageModel(environment.card_db)
card = environment.card_db.get_card_with_scryfall_id("b3b87bfc-f97f-4734-94f6-e3e2f335fc4d", is_front)
|
︙ | | |
Changes to tests/test_page_layout_settings.py.
︙ | | |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
-
+
+
|
import mtg_proxy_printer.settings
import mtg_proxy_printer.model.document
import mtg_proxy_printer.model.document_loader
from mtg_proxy_printer.units_and_sizes import PageType, QuantityT, UnitT, unit_registry, StrDict
from mtg_proxy_printer.ui.page_scene import RenderMode
from PyQt5.QtGui import QPageLayout, QPageSize
from PyQt5.QtCore import QMarginsF
from PySide6.QtGui import QPageLayout, QPageSize
from PySide6.QtCore import QMarginsF
import pytest
from hamcrest import *
PageLayoutSettings = mtg_proxy_printer.model.document_loader.PageLayoutSettings
from tests.hasgetter import has_getters
from tests.helpers import quantity_close_to
|
︙ | | |
Changes to tests/ui/settings/test_card_filter_widgets.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing
from unittest.mock import patch
from PyQt5.QtWidgets import QCheckBox
from PySide6.QtWidgets import QCheckBox
import pytest
from hamcrest import *
from mtg_proxy_printer.units_and_sizes import SectionProxy
import mtg_proxy_printer.settings
from mtg_proxy_printer.ui.printing_filter_widgets import AbstractPrintingFilter, GeneralPrintingFilter, \
FormatPrintingFilter
|
︙ | | |
Changes to tests/ui/settings/test_initial_page_selection.py.
︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
+
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from PyQt5.QtCore import QStringListModel
from PySide6.QtCore import QStringListModel
from hamcrest import *
from pytestqt.qtbot import QtBot
from mtg_proxy_printer.ui.settings_window import SettingsWindow
|
︙ | | |
Changes to tests/ui/test_add_card.py.
︙ | | |
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
-
+
+
|
import typing
import pytest
from hamcrest import *
from pytestqt.qtbot import QtBot
from PyQt5.QtCore import Qt, QPoint, QRect, QItemSelectionModel
from PyQt5.QtWidgets import QDialogButtonBox
from PySide6.QtCore import Qt, QPoint, QRect, QItemSelectionModel
from PySide6.QtWidgets import QDialogButtonBox
from mtg_proxy_printer.model.carddb import CardDatabase, CardIdentificationData
from mtg_proxy_printer.ui.add_card import HorizontalAddCardWidget, VerticalAddCardWidget
from tests.helpers import fill_card_database_with_json_card
StringList = typing.List[str]
|
︙ | | |
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
+
+
+
-
+
+
+
|
"""
fill_card_database_with_json_card(qtbot, card_db, "english_double_faced_art_series_card")
expected_card_identification_data = CardIdentificationData(
"en", "Clearwater Pathway", "aznr", "25"
)
qtbot.add_widget(add_card_widget := widget_class())
add_card_widget.set_card_database(card_db)
add_card_widget.card_name_filter_updated("") # Populate the card name list
add_card_widget.ui.card_name_list.setSelection(QRect(1, 1, 1, 1), ClearAndSelect)
qtbot.mouseClick(add_card_widget.ui.card_name_list, LeftButton, pos=QPoint(10, 10))
add_card_widget.ui.copies_input.setValue(1)
ok_button = add_card_widget.ui.button_box.button(StandardButton.Ok)
qtbot.mouseClick(ok_button, LeftButton, pos=QPoint(10, 10))
add_card_widget.ui.card_name_list.setSelection(QRect(1, 1, 1, 1), ClearAndSelect)
qtbot.mouseClick(add_card_widget.ui.card_name_list, LeftButton, pos=QPoint(10, 10))
qtbot.wait(10)
qtbot.mouseClick(
add_card_widget.ui.button_box.button(StandardButton.Ok), LeftButton
)
add_card_widget.ui.copies_input.setValue(1)
assert_that(add_card_widget._read_card_data_from_ui(), is_(equal_to(expected_card_identification_data)))
|
Changes to tests/ui/test_card_item.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
-
-
+
+
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from hamcrest import *
import pytest
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QColorConstants, QPixmap, QImage, QPainter, QColor
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsScene
from PySide6.QtCore import QSize
from PySide6.QtGui import QColorConstants, QPixmap, QImage, QPainter, QColor
from PySide6.QtWidgets import QGraphicsItem, QGraphicsScene
from mtg_proxy_printer.ui.page_scene import CardItem
from tests.document_controller.helpers import append_new_card_in_page
from tests.hasgetter import has_getter
|
︙ | | |
Changes to tests/ui/test_central_widget.py.
︙ | | |
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
-
+
+
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pathlib import PurePath
from unittest.mock import NonCallableMagicMock, patch
import pytest
from pytestqt.qtbot import QtBot
from PyQt5.QtCore import Qt
from PySide6.QtCore import Qt
from hamcrest import *
from mtg_proxy_printer.model.document_page import Page
from mtg_proxy_printer.model.carddb import Card, MTGSet, CheckCard
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard
# Import dynamically used by pytest. Without this, the main_window fixture won’t be found by pytest.
|
︙ | | |
Changes to tests/ui/test_deck_import_wizard.py.
︙ | | |
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
-
-
-
+
+
+
|
import unittest.mock
from unittest.mock import MagicMock
from hamcrest import *
from pytestqt.qtbot import QtBot
import pytest
from PyQt5.QtCore import QStringListModel, Qt, QPoint, QObject
from PyQt5.QtWidgets import QCheckBox, QWizard, QTableView, QComboBox, QLineEdit
from PyQt5.QtTest import QTest
from PySide6.QtCore import QStringListModel, Qt, QPoint, QObject
from PySide6.QtWidgets import QCheckBox, QWizard, QTableView, QComboBox, QLineEdit
from PySide6.QtTest import QTest
import mtg_proxy_printer.settings
from mtg_proxy_printer.model.carddb import CardDatabase, CardIdentificationData, CardList
from mtg_proxy_printer.ui.deck_import_wizard import DeckImportWizard
from mtg_proxy_printer.decklist_parser.re_parsers import MTGOnlineParser, MTGArenaParser, \
GenericRegularExpressionDeckParser
from mtg_proxy_printer.model.card_list import PageColumns
|
︙ | | |
Changes to tests/ui/test_item_delegate.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import Counter
import itertools
from unittest.mock import NonCallableMagicMock
from PyQt5.QtWidgets import QComboBox
from PySide6.QtWidgets import QComboBox
import pytest
from hamcrest import *
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard
from mtg_proxy_printer.model.carddb import CardDatabase, Card, MTGSet
from mtg_proxy_printer.model.card_list import CardListModel
from mtg_proxy_printer.model.document import Document
|
︙ | | |
Changes to tests/ui/test_main_window.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
-
-
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import dataclasses
import pathlib
import unittest.mock
from PyQt5.QtCore import QStringListModel, QThreadPool
from PyQt5.QtWidgets import QMessageBox
from PySide6.QtCore import QStringListModel, QThreadPool
from PySide6.QtWidgets import QMessageBox
from pytestqt.qtbot import QtBot
from hamcrest import *
import pytest
import mtg_proxy_printer.http_file
import mtg_proxy_printer.downloader_base
from mtg_proxy_printer.sqlite_helpers import open_database
|
︙ | | |
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
-
-
|
cid = CardInfoDownloader(card_db)
main_window = MainWindow(card_db, cid, document.image_db, document, QStringListModel(["en"]))
qtbot.add_widget(main_window)
with qtbot.wait_exposed(main_window, timeout=1000):
main_window.show()
yield main_window
main_window.hide()
del cid
main_window.__dict__.clear()
def test_main_window_hides_progress_bar_after_downloading_image_during_load(
qtbot: QtBot, main_window: MainWindow):
with unittest.mock.patch.object( # Mock all HTTP-specific I/O calls
mtg_proxy_printer.downloader_base.mtg_proxy_printer.http_file.MeteredSeekableHTTPFile,
"_read_content_length") as cl_mock, \
|
︙ | | |
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
-
-
|
unittest.mock.patch(
"mtg_proxy_printer.card_info_downloader.CardInfoDatabaseImportWorker.import_card_data_from_online_api") as import_from_api, \
unittest.mock.patch.object(QThreadPool.globalInstance(), "start") as thread_pool_start, \
qtbot.assertNotEmitted(main_window.loading_state_changed):
main_window.show_card_data_update_available_message_box(10000)
thread_pool_start.assert_not_called()
import_from_api.assert_not_called()
assert_that(ui.action_download_card_data.isEnabled(), is_(True))
def test_accepting_card_data_update_offer_results_in_performed_action(qtbot: QtBot, main_window: MainWindow):
ui = main_window.ui
ui.action_download_card_data.setEnabled(True)
with unittest.mock.patch.object(
mtg_proxy_printer.ui.main_window.QMessageBox,
"question", return_value=StandardButton.Yes) as message_box, \
unittest.mock.patch.object(QThreadPool.globalInstance(), "start") as thread_pool_start:
main_window.show_card_data_update_available_message_box(10000)
message_box.assert_called_once()
thread_pool_start.assert_called_once()
assert_that(ui.action_download_card_data.isEnabled(), is_(False))
def test_action_download_card_data_is_enabled_after_network_error(qtbot: QtBot, main_window: MainWindow):
ui = main_window.ui
ui.action_download_card_data.setEnabled(False)
with unittest.mock.patch.object(
mtg_proxy_printer.ui.main_window.QMessageBox, "warning", return_value=StandardButton.Ok
|
︙ | | |
Changes to tests/ui/test_page_config_container.py.
︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
+
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from PyQt5.QtWidgets import QCheckBox, QDoubleSpinBox, QLineEdit
from PySide6.QtWidgets import QCheckBox, QDoubleSpinBox, QLineEdit
import pytest
from pytestqt.qtbot import QtBot
from hamcrest import *
from mtg_proxy_printer.model.document_loader import PageLayoutSettings
from mtg_proxy_printer.units_and_sizes import QuantityT, unit_registry
|
︙ | | |
Changes to tests/ui/test_page_config_dialog.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from PyQt5.QtWidgets import QDialogButtonBox
from PySide6.QtWidgets import QDialogButtonBox
from mtg_proxy_printer.ui.dialogs import DocumentSettingsDialog
StandardButton = QDialogButtonBox.StandardButton
def test__init__(qtbot, document_light):
"""Ensure that the dialog can be instantiated"""
DocumentSettingsDialog(document_light)
|
︙ | | |
Changes to tests/ui/test_page_config_widget.py.
︙ | | |
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
|
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from unittest.mock import patch
import pint
from PyQt5.QtWidgets import QDoubleSpinBox, QCheckBox, QLineEdit
from PySide6.QtWidgets import QDoubleSpinBox, QCheckBox, QLineEdit
from hamcrest import *
import pytest
from pytestqt.qtbot import QtBot
from mtg_proxy_printer.model.document_loader import PageLayoutSettings
import mtg_proxy_printer.settings
|
︙ | | |
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
+
|
assert_that(ui, has_property(attribute_name, instance_of(QLineEdit)))
assert_that(widget.page_layout, has_property(attribute_name, instance_of(str)))
line_edit: QLineEdit = getattr(ui, attribute_name)
new_value = "Test"
with qtbot.waitSignals([line_edit.textChanged, widget.page_layout_changed], timeout=100):
line_edit.setText(new_value)
assert_that(widget.page_layout, has_property(attribute_name, equal_to(new_value)))
ZeroMarginsSettings = {
"paper-height": "297 mm",
"paper-width": "210 mm",
"margin-top": "0 mm",
"margin-bottom": "0 mm",
|
︙ | | |
Changes to tests/ui/test_page_renderer.py.
︙ | | |
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
-
+
+
|
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from unittest.mock import patch
import pytest
from hamcrest import *
from PyQt5.QtCore import QEvent
from PyQt5.QtWidgets import QAction
from PySide6.QtCore import QEvent
from PySide6.QtGui import QAction
from mtg_proxy_printer.ui.page_renderer import PageRenderer, ZoomDirection
PATH_PREFIX = "mtg_proxy_printer.ui.page_renderer."
@pytest.fixture
|
︙ | | |
50
51
52
53
54
55
56
57
|
50
51
52
53
54
55
56
57
|
-
+
|
@pytest.mark.parametrize("zoom_action, direction", [
("zoom_in_action", ZoomDirection.IN), ("zoom_out_action", ZoomDirection.OUT)])
def test_renderer_zoom_action_triggers_zoom(renderer: PageRenderer, zoom_action: str, direction: ZoomDirection):
action: QAction = getattr(renderer, zoom_action)
action.trigger()
renderer._perform_zoom_step.assert_called_once_with(direction, False)
renderer._perform_zoom_step.assert_called_once_with(direction)
|
Changes to tests/ui/test_page_scene.py.
︙ | | |
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
-
-
-
+
+
+
|
import typing
from unittest.mock import patch
from math import ceil
from hamcrest import *
import pytest
from PyQt5.QtWidgets import QGraphicsPixmapItem, QGraphicsLineItem
from PyQt5.QtGui import QPalette, QColorConstants, QPixmap, QImage, QColor, QPainter
from PyQt5.QtCore import QPoint
from PySide6.QtWidgets import QGraphicsPixmapItem, QGraphicsLineItem
from PySide6.QtGui import QPalette, QColorConstants, QPixmap, QImage, QColor, QPainter
from PySide6.QtCore import QPoint
from mtg_proxy_printer.units_and_sizes import PageType, CardSizes, CardSize, UnitT, unit_registry, QuantityT
from mtg_proxy_printer.ui.page_scene import RenderMode, PageScene
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard, ActionRemoveCards
from mtg_proxy_printer.document_controller.compact_document import ActionCompactDocument
from mtg_proxy_printer.model.document import Document
|
︙ | | |
Changes to tests/ui/test_progress_bar.py.
︙ | | |
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
-
+
|
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from hamcrest import *
import pytest
from PyQt5.QtWidgets import QWidget
from PySide6.QtWidgets import QWidget
from pytestqt.qtbot import QtBot
from mtg_proxy_printer.ui.progress_bar import ProgressBar
from tests.hasgetter import has_getters
INNER_ELEMENTS = ["inner_progress_bar", "inner_progress_label"]
|
︙ | | |