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 69fbf844ab to c55f09b869
|
2025-08-05
| ||
| 13:49 | Prepare release v0.33.2 check-in: 433c3ffe46 user: thomas tags: v0.33.x-port_PySide6 | |
| 13:08 | Backport fix for ActionLoadDocument to version 0.33. check-in: 574070a05a user: thomas tags: v0.33.x | |
|
2025-07-28
| ||
| 16:54 | Modernize the application tech stack: Drop support for outdated libraries and platforms: PyQt5, Pint < 0.22, Windows 7, Python 3.8, 3.9, 3.10. Use modern type hinting style. Remove compatibility workarounds that ensured compatibility with Python 3.8 and Python-3.8-compatible dependencies. check-in: fd347b46df user: thomas tags: trunk | |
| 15:43 | Merge the PySide6 port. check-in: f3a5fe38f7 user: thomas tags: modernize_tech_stack | |
| 12:50 | Drop support for Python 3.8, 3.9, 3.10. Raise minimum required version of Pint to 0.22. Drop conditional dependencies on certifi, as that was used for 3.8 compatibility. Drop dependency on typing_extensions, because it was used for backwards compatibility with 3.8 check-in: 3d944bafec user: thomas tags: modernize_tech_stack | |
|
2025-07-25
| ||
| 16:17 | Merge with trunk check-in: f3faf688e9 user: thomas tags: refactor_async_tasks | |
| 13:56 | Release v0.33.1 Closed-Leaf check-in: c55f09b869 user: thomas tags: port_pyside6 | |
| 13:51 | Release v0.33.1 check-in: 69fbf844ab user: thomas tags: trunk, release, v0.33.1 | |
| 10:21 | Release v0.33.0 check-in: b73f214010 user: thomas tags: port_pyside6 | |
| 10:20 | Release v0.33.0 check-in: b4584e799d user: thomas tags: trunk, release, v0.33.0 | |
Changes to README.md.
| ︙ | ︙ | |||
112 113 114 115 116 117 118 | - Python >= 3.8 These external libraries are used in the code. They can be installed from PyPI. - `platformdirs` - `ijson` - `pint` | | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | - Python >= 3.8 These external libraries are used in the code. They can be installed from PyPI. - `platformdirs` - `ijson` - `pint` - `PySide6` - `delegateto` - `PyHamcrest` - `cx_Freeze` (Stand-alone bundles only. Used by the installer for Windows®-based platforms.) - Either `truststore` (Py >= 3.10) or `certifi` (Py < 3.10) - `typing_extensions` (Py < 3.11) ### System libraries |
| ︙ | ︙ |
Changes to build_MTGProxyPrinter_packages.bat.
1 2 3 | :: Generate an application bundle using cx_Freeze :: Create or activate the build environment | | | | 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-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 | #!/bin/bash | | | 1 2 3 4 5 6 7 8 9 |
#!/bin/bash
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 | python -m venv venv-PySide6 call venv-PySide6\Scripts\activate.bat python -m pip install --upgrade pip setuptools python -m pip install wheel "pip-tools >= 7" echo Creating requirements.txt from pyproject.toml. This takes a while. python scripts\rebuild_requirements.py echo "Installing dependencies into the virtual environment" |
| ︙ | ︙ |
Changes to create_development_environment.sh.
1 | #!/bin/bash | | | 1 2 3 4 5 6 7 8 9 |
#!/bin/bash
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 | 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. | | | | < | < < | 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. # Qt6, PySide6 Qt and PySide6 are available under the GNU Lesser General Public License version 3. The Qt Toolkit is Copyright (C) 2018 The Qt Company Ltd. and other contributors. Contact: https://www.qt.io/licensing/ # 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.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | # 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 | | < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # 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 PySide6.QtCore import QTimer 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 |
| ︙ | ︙ | |||
57 58 59 60 61 62 63 |
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()
| < < < | | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
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()
_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()
logger.debug("Left event loop.")
if __name__ == "__main__":
main()
|
Changes to mtg_proxy_printer/application.py.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 | import pathlib import platform import shutil import sys from tempfile import mkdtemp import typing | | | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import pathlib import platform import shutil import sys from tempfile import mkdtemp import typing 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 |
| ︙ | ︙ | |||
55 56 57 58 59 60 61 |
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__}")
| < < < < < < > > > > > > > | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
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__}")
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?
|
| ︙ | ︙ | |||
225 226 227 228 229 230 231 |
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}'")
| | | 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
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.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)
|
| ︙ | ︙ | |||
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}'")
| < < | 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
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}'")
@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.
| ︙ | ︙ | |||
26 27 28 29 30 31 32 | import socket import typing import urllib.error import urllib.parse import urllib.request import ijson | | | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import socket import typing import urllib.error import urllib.parse import urllib.request import ijson 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.
| ︙ | ︙ | |||
29 30 31 32 33 34 35 | import time import typing import urllib.error import urllib.parse from textwrap import dedent from typing import List, Dict, Union, Tuple, Any, Generator, Callable, Iterable | | | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import time
import typing
import urllib.error
import urllib.parse
from textwrap import dedent
from typing import List, Dict, Union, Tuple, Any, Generator, Callable, Iterable
from PySide6.QtCore import QCoreApplication, Qt
try:
from typing import LiteralString
except ImportError:
from typing_extensions import LiteralString
from mtg_proxy_printer.progress_meter import ProgressMeter
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/decklist_downloader.py.
| ︙ | ︙ | |||
25 26 27 28 29 30 31 | import urllib.parse from io import StringIO import platform import re import typing import ijson | | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import urllib.parse
from io import StringIO
import platform
import re
import typing
import ijson
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.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 abc import abstractmethod import typing from PySide6.QtCore import QObject, Signal from mtg_proxy_printer.model.carddb import CardDatabase, CardIdentificationData from mtg_proxy_printer.model.card import Card, AnyCardType 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__) |
| ︙ | ︙ |
Changes to mtg_proxy_printer/decklist_parser/csv_parsers.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import abc import collections import csv import typing | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import abc import collections import csv import typing from PySide6.QtCore import QObject, QCoreApplication from mtg_proxy_printer.model.carddb import CardDatabase, CardIdentificationData from ..model.card import Card from mtg_proxy_printer.model.imagedb import ImageDatabase from .common import ParsedDeck, ParserBase from mtg_proxy_printer.logger import get_logger |
| ︙ | ︙ |
Changes to mtg_proxy_printer/decklist_parser/re_parsers.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import copy from collections import Counter import re import typing | | | 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 copy from collections import Counter import re import typing from PySide6.QtCore import QObject, QCoreApplication from mtg_proxy_printer.decklist_parser.common import ParsedDeck, ParserBase from mtg_proxy_printer.model.carddb import CardDatabase, CardIdentificationData from mtg_proxy_printer.model.card import Card from mtg_proxy_printer.model.imagedb import ImageDatabase from mtg_proxy_printer.logger import get_logger logger = get_logger(__name__) |
| ︙ | ︙ | |||
175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
@staticmethod
def supported_file_types() -> typing.Dict[str, typing.List[str]]:
return {
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: {1,2})?(?P<copies>\d+) \[(?P<set_code>\w+)?] (?P<name>.+)"), parent
)
| > | 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
@staticmethod
def supported_file_types() -> typing.Dict[str, typing.List[str]]:
return {
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: {1,2})?(?P<copies>\d+) \[(?P<set_code>\w+)?] (?P<name>.+)"), parent
)
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/document_controller/_interface.py.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | from abc import abstractmethod from functools import partial import itertools import operator import typing | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from abc import abstractmethod
from functools import partial
import itertools
import operator
import typing
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/edit_custom_card.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 typing
from PySide6.QtCore import QModelIndex, Qt
if typing.TYPE_CHECKING:
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.document_page import CardContainer, PageColumns
from ._interface import DocumentAction, Self
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/document_controller/edit_document_settings.py.
| ︙ | ︙ | |||
76 77 78 79 80 81 82 |
for partition in page_partitions: # type: int, PageType, typing.List[Page]
self._reflow_partition(document, partition)
@staticmethod
def _partition_pages_by_accepting_card_size(document: "Document") -> typing.List[PagePartition]:
"""
Partitions the document pages into consecutive lists of pages.
| | | 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
for partition in page_partitions: # type: int, PageType, typing.List[Page]
self._reflow_partition(document, partition)
@staticmethod
def _partition_pages_by_accepting_card_size(document: "Document") -> typing.List[PagePartition]:
"""
Partitions the document pages into consecutive lists of pages.
Each partition only contains pages with exactly one page-type (REGULAR or OVERSIZED) plus empty pages.
Leading empty pages are ignored.
"""
pages = document.pages
first_populated_page = sum(1 for _ in itertools.takewhile(lambda p: not p, pages))
if first_populated_page == len(pages):
return []
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/document_controller/move_cards.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import functools import itertools import typing | | | 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 functools
import itertools
import typing
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.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 typing
from PySide6.QtCore import Qt
from ..model.card import Card
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.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 |
from random import randbytes
except ImportError:
# Compatibility with Py 3.8
from secrets import token_bytes as randbytes
import typing
| | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from random import randbytes
except ImportError:
# Compatibility with Py 3.8
from secrets import token_bytes as randbytes
import typing
from PySide6.QtCore import Qt, QModelIndex
from ._interface import DocumentAction, IllegalStateError, Self
from ..model.card import Card
from mtg_proxy_printer.model.document_page import CardContainer, PageColumns
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.units_and_sizes import PageType
__all__ = [
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/downloader_base.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 gzip 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.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 | import http.client import socket import time from typing import List, Optional, Dict, Callable import urllib.error import urllib.request | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import http.client import socket import time from typing import List, Optional, Dict, Callable import urllib.error import urllib.request 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.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | # 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" | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 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.33.1+PySide6"
COPYRIGHT = "(C) 2020-2025 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.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. from typing import Iterable, List, Optional, BinaryIO, Union from io import BufferedIOBase | | | 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 typing import Iterable, List, Optional, BinaryIO, Union from io import BufferedIOBase 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.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 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.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import dataclasses import hashlib import enum import functools import typing | | | > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import dataclasses
import hashlib
import enum
import functools
import typing
from PySide6.QtCore import QRect, QPoint, QSize, Qt, QPointF
from PySide6.QtGui import QPixmap, QColor, QColorConstants, QPainter, QTransform
from mtg_proxy_printer.units_and_sizes import CardSize, PageType, CardSizes, UUID
ItemDataRole = Qt.ItemDataRole
RenderHint = QPainter.RenderHint
SmoothTransformation = Qt.TransformationMode.SmoothTransformation
IgnoreAspectRatio = Qt.AspectRatioMode.IgnoreAspectRatio
@dataclasses.dataclass(frozen=True)
class MTGSet:
code: str
name: str
def data(self, role: ItemDataRole):
|
| ︙ | ︙ | |||
261 262 263 264 265 266 267 |
# 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() / (2 * card_size.width())
combined_image = QPixmap(card_size)
combined_image.fill(QColorConstants.Transparent)
painter = QPainter(combined_image)
| | | 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# 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() / (2 * card_size.width())
combined_image = QPixmap(card_size)
combined_image.fill(QColorConstants.Transparent)
painter = QPainter(combined_image)
painter.setRenderHints(RenderHint.SmoothPixmapTransform)
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/card_list.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | from collections import Counter import dataclasses import enum import itertools import typing | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | from collections import Counter import dataclasses import enum import itertools import typing from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal, QItemSelection from PySide6.QtGui import QIcon from mtg_proxy_printer.document_controller import DocumentAction from mtg_proxy_printer.document_controller.edit_custom_card import ActionEditCustomCard from mtg_proxy_printer.model.document import Document from mtg_proxy_printer.model.document_page import PageColumns from mtg_proxy_printer.ui.common import get_card_image_tooltip from mtg_proxy_printer.decklist_parser.common import CardCounter |
| ︙ | ︙ | |||
132 133 134 135 136 137 138 |
return get_card_image_tooltip(card.source_image_file)
elif card.is_oversized and role == ItemDataRole.ToolTipRole:
return self.tr("Beware: Potentially oversized card!\nThis card may not fit in your deck.")
if card.is_oversized and role == ItemDataRole.DecorationRole:
return self._oversized_icon
return None
| | | 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
return get_card_image_tooltip(card.source_image_file)
elif card.is_oversized and role == ItemDataRole.ToolTipRole:
return self.tr("Beware: Potentially oversized card!\nThis card may not fit in your deck.")
if card.is_oversized and role == ItemDataRole.DecorationRole:
return self._oversized_icon
return None
def flags(self, index: QModelIndex) -> ItemFlag:
flags = super().flags(index)
if index.column() in self.EDITABLE_COLUMNS or self.rows[index.row()].card.is_custom_card:
flags |= ItemFlag.ItemIsEditable
return flags
def setData(self, index: QModelIndex, value: typing.Any, role: ItemDataRole = ItemDataRole.EditRole) -> bool:
row, column = index.row(), index.column()
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/model/carddb.py.
| ︙ | ︙ | |||
24 25 26 27 28 29 30 |
import threading
try:
from typing import LiteralString
except ImportError:
from typing_extensions import LiteralString
from typing import NamedTuple, TypeVar, Set, Optional, Dict, Literal, List, Sequence, Any, Union, Tuple
| | | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import threading
try:
from typing import LiteralString
except ImportError:
from typing_extensions import LiteralString
from typing import NamedTuple, TypeVar, Set, Optional, Dict, Literal, List, Sequence, Any, Union, Tuple
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QObject, Signal, Slot
from mtg_proxy_printer.model.card import MTGSet, Card, CheckCard, OptionalCard, CardList, CustomCard
from mtg_proxy_printer.model.imagedb_files import CacheContent
import mtg_proxy_printer.app_dirs
from mtg_proxy_printer.natsort import natural_sorted
import mtg_proxy_printer.meta_data
from mtg_proxy_printer.sqlite_helpers import cached_dedent, open_database, validate_database_schema
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/model/document.py.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 | import enum import itertools import math import pathlib import sys import typing | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import enum
import itertools
import math
import pathlib
import sys
import typing
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt, Slot, Signal, \
QPersistentModelIndex
from mtg_proxy_printer.model.imagedb_files import ImageKey
from mtg_proxy_printer.natsort import to_list_of_ranges
from mtg_proxy_printer.document_controller.edit_custom_card import ActionEditCustomCard
from mtg_proxy_printer.model.document_page import CardContainer, Page, PageColumns
from mtg_proxy_printer.units_and_sizes import PageType, CardSizes, CardSize
|
| ︙ | ︙ | |||
219 220 221 222 223 224 225 |
if not index.isValid():
return None
if isinstance(index.internalPointer(), CardContainer): # Card
return self._data_card(index, role)
else: # Page
return self._data_page(index, role)
| | | 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
if not index.isValid():
return None
if isinstance(index.internalPointer(), CardContainer): # Card
return self._data_card(index, role)
else: # Page
return self._data_page(index, role)
def flags(self, index: AnyIndex) -> Qt.ItemFlag:
index = self._to_index(index)
data = index.internalPointer()
flags = super().flags(index)
if isinstance(data, CardContainer) and (index.column() in self.EDITABLE_COLUMNS or data.card.is_custom_card):
flags |= ItemFlag.ItemIsEditable
return flags
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/model/document_loader.py.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | import itertools import pathlib import sqlite3 import textwrap from typing import Counter, Dict, Iterable, List, NamedTuple, Optional, Tuple, TYPE_CHECKING import pint | | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import itertools
import pathlib
import sqlite3
import textwrap
from typing import Counter, Dict, Iterable, List, NamedTuple, Optional, Tuple, TYPE_CHECKING
import pint
from PySide6.QtGui import QPageLayout, QPageSize, QColor
from PySide6.QtCore import QObject, Signal, QThreadPool, Qt
from hamcrest import assert_that, all_of, instance_of, greater_than_or_equal_to, matches_regexp, is_in, \
has_properties, is_, any_of, none, has_item, has_property, equal_to
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 | import shutil import socket import string import threading import typing import urllib.error | | | | 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 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/imagedb_files.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 dataclasses import pathlib from PySide6.QtCore import Qt import mtg_proxy_printer.app_dirs import mtg_proxy_printer.http_file from mtg_proxy_printer.logger import get_logger logger = get_logger(__name__) del get_logger ItemDataRole = Qt.ItemDataRole |
| ︙ | ︙ |
Changes to mtg_proxy_printer/model/page_layout.py.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import dataclasses import itertools import math import typing from typing import Generator, Tuple import pint | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import dataclasses
import itertools
import math
import typing
from typing import Generator, Tuple
import pint
from PySide6.QtGui import QPageLayout, QPageSize, QColor, QColorConstants
from PySide6.QtCore import QMarginsF, QSizeF
try:
from hamcrest import contains_exactly
except ImportError:
# Compatibility with PyHamcrest < 1.10
from hamcrest import contains as contains_exactly
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/model/string_list.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 PySide6.QtCore import QAbstractListModel, Qt, QObject, QModelIndex
from mtg_proxy_printer.model.card import MTGSet
__all__ = [
"PrettySetListModel",
]
INVALID_INDEX = QModelIndex()
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/natsort.py.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 | Natural sorting for lists or other iterables of strings. """ import itertools import re import typing | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
Natural sorting for lists or other iterables of strings.
"""
import itertools
import re
import typing
from PySide6.QtCore import QSortFilterProxyModel, QModelIndex
__all__ = [
"natural_sorted",
"str_less_than",
"NaturallySortedSortFilterProxyModel",
"to_list_of_ranges"
]
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/print.py.
| ︙ | ︙ | |||
21 22 23 24 25 26 27 |
from os import process_cpu_count
except ImportError:
from os import cpu_count as process_cpu_count
from pathlib import Path
from threading import BoundedSemaphore
import typing
| | | | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
from os import process_cpu_count
except ImportError:
from os import cpu_count as process_cpu_count
from pathlib import Path
from threading import BoundedSemaphore
import typing
from PySide6.QtCore import QObject, QMarginsF, QSizeF, Slot, QPersistentModelIndex, QThreadPool
from PySide6.QtGui import QPainter, QPdfWriter, QPageSize, QImage, QColor
from PySide6.QtPrintSupport import QPrinter
from mtg_proxy_printer.runner import ProgressSignalContainer
if typing.TYPE_CHECKING:
from mtg_proxy_printer.ui.main_window import MainWindow
from mtg_proxy_printer.units_and_sizes import RESOLUTION
|
| ︙ | ︙ | |||
128 129 130 131 132 133 134 |
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
| < | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
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.setDuplex(QPrinter.DuplexMode.DuplexNone)
printer.setOutputFormat(QPrinter.OutputFormat.NativeFormat)
if RenderMode.IMPLICIT_MARGINS not in renderer.render_mode:
printer.setFullPage(True)
return printer
|
| ︙ | ︙ | |||
214 215 216 217 218 219 220 |
self.painter.end()
logger.info("Writing document finished.")
def _switch_to_page(self, page_number: int):
"""Render the given page on the internal scene"""
index = QPersistentModelIndex(self.document.index(page_number, 0))
self.scene.on_current_page_changed(index)
| < | 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
self.painter.end()
logger.info("Writing document finished.")
def _switch_to_page(self, page_number: int):
"""Render the given page on the internal scene"""
index = QPersistentModelIndex(self.document.index(page_number, 0))
self.scene.on_current_page_changed(index)
class Renderer(QObject):
def __init__(self, document: Document, parent: QObject = None):
super().__init__(parent)
self.document = document
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/printing_filter_updater.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 sqlite3
import typing
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 |
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="renderHints">
| | | 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</set>
</property>
</widget>
</item>
<item row="4" column="2" rowspan="2">
<widget class="PageCardTableView" 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 |
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="renderHints">
| | | 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</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/runner.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 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/save_file_migrations.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 textwrap | | | | 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 sqlite3
import textwrap
from PySide6.QtCore import QSizeF
from PySide6.QtGui import QPageSize, QPageLayout
from mtg_proxy_printer.units_and_sizes import PageSizeManager
try:
from hamcrest import contains_exactly
except ImportError:
# Compatibility with PyHamcrest < 1.10
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/settings.py.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | import pathlib import re import typing import tokenize from collections import defaultdict import pint | | | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import pathlib
import re
import typing
import tokenize
from collections import defaultdict
import pint
from PySide6.QtCore import QStandardPaths, QLocale
from PySide6.QtGui import QPageSize, QPageLayout, QColor
from PySide6.QtPrintSupport import QPrinterInfo
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, PageSizeManager, is_acceptable_page_size, \
Unit
|
| ︙ | ︙ | |||
72 73 74 75 76 77 78 |
Territory.UnitedStatesMinorOutlyingIslands: Letter,
Territory.UnitedStatesVirginIslands: Letter,
Territory.Venezuela: Letter,
})
def get_default_paper_size() -> str:
| | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
Territory.UnitedStatesMinorOutlyingIslands: Letter,
Territory.UnitedStatesVirginIslands: Letter,
Territory.Venezuela: Letter,
})
def get_default_paper_size() -> str:
system_country = QLocale.system().territory()
default = PageSizeManager.PageSizeReverse[LOCATION_PAPER_SIZE_TABLE[system_country]]
printer_info = QPrinterInfo.defaultPrinter()
if printer_info.isNull():
return default
page_size = printer_info.defaultPageSize()
if page_size.isValid() and is_acceptable_page_size(page_size):
return PageSizeManager.PageSizeReverse[page_size.id()]
|
| ︙ | ︙ | |||
540 541 542 543 544 545 546 |
def _validate_string_is_in_set(section: SectionProxy, defaults: SectionProxy, valid_options: typing.Set[str], key: str):
"""Checks if the value of the option is one of the allowed values, as determined by the given set of strings."""
if section[key] not in valid_options:
_restore_default(section, defaults, key)
def _validate_color(section: SectionProxy, defaults: SectionProxy, key: str):
| | | 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 |
def _validate_string_is_in_set(section: SectionProxy, defaults: SectionProxy, valid_options: typing.Set[str], key: str):
"""Checks if the value of the option is one of the allowed values, as determined by the given set of strings."""
if section[key] not in valid_options:
_restore_default(section, defaults, key)
def _validate_color(section: SectionProxy, defaults: SectionProxy, key: str):
if not QColor.isValidColorName(section.get(key)):
_restore_default(section, defaults, key)
def _restore_default(section: SectionProxy, defaults: SectionProxy, key: str):
section[key] = defaults[key]
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/add_card.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | | | 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/>. from typing import Union, Type, Optional from PySide6.QtCore import QStringListModel, Slot, Signal, Qt, QItemSelectionModel, QItemSelection from PySide6.QtWidgets import QWidget, QDialogButtonBox from PySide6.QtGui import QIcon import mtg_proxy_printer.model.card 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 |
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/cache_cleanup_wizard.py.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | import dataclasses import datetime import enum import math import pathlib import typing | | | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import dataclasses import datetime import enum import math import pathlib import typing from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex, QObject, QItemSelectionModel, QSize from PySide6.QtGui import QIcon 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 from mtg_proxy_printer.model.card import MTGSet, Card from mtg_proxy_printer.model.imagedb import ImageDatabase from mtg_proxy_printer.model.imagedb_files import CacheContent as ImageCacheContent, ImageKey |
| ︙ | ︙ | |||
445 446 447 448 449 450 451 |
BUTTON_ICONS = {
QWizard.WizardButton.FinishButton: "edit-delete",
QWizard.WizardButton.CancelButton: "dialog-cancel",
QWizard.WizardButton.HelpButton: "help-contents",
}
def __init__(self, card_db: CardDatabase, image_db: ImageDatabase,
| | | 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
BUTTON_ICONS = {
QWizard.WizardButton.FinishButton: "edit-delete",
QWizard.WizardButton.CancelButton: "dialog-cancel",
QWizard.WizardButton.HelpButton: "help-contents",
}
def __init__(self, card_db: CardDatabase, image_db: ImageDatabase,
parent: QWidget = None, flags=Qt.WindowType.Window):
super().__init__(QSize(1024, 768), parent, flags)
self.image_db = image_db
self.addPage(FilterSetupPage(self))
self.addPage(CardFilterPage(card_db, image_db, self))
self.addPage(SummaryPage(self))
self.setWindowTitle(self.tr("Cleanup locally stored card images", "Dialog window title"))
self.setWindowIcon(QIcon.fromTheme("edit-clear-history"))
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/card_list_table_view.py.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | # 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 math | | | | 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 math
from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtWidgets import QTableView, QWidget
from mtg_proxy_printer.model.card_list import CardListColumns, CardListModel
from mtg_proxy_printer.natsort import NaturallySortedSortFilterProxyModel
from mtg_proxy_printer.ui.item_delegates import CollectorNumberEditorDelegate, BoundedCopiesSpinboxDelegate, \
CardSideSelectionDelegate, SetEditorDelegate, LanguageEditorDelegate
from mtg_proxy_printer.logger import get_logger
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/central_widget.py.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | # 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 | | | | 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/>. from typing import Union, Type from PySide6.QtCore import Slot, QItemSelectionModel, QModelIndex from PySide6.QtWidgets import QWidget import mtg_proxy_printer.app_dirs import mtg_proxy_printer.settings from mtg_proxy_printer.model.document import Document from mtg_proxy_printer.model.carddb import CardDatabase from mtg_proxy_printer.model.imagedb import ImageDatabase |
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/common.py.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 |
cache = lru_cache
from pathlib import Path
import platform
import re
from typing import Union, Dict
| | | | < | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
cache = lru_cache
from pathlib import Path
import platform
import re
from typing import Union, Dict
from PySide6.QtCore import QFile, QObject, QSize, QCoreApplication, Qt, QBuffer, QIODevice
from PySide6.QtWidgets import QWizard, QWidget, QGraphicsColorizeEffect, QTextEdit, QDialog
from PySide6.QtGui import QIcon, QPixmap, QColor, QColorConstants
from PySide6.QtUiTools import loadUiType
import mtg_proxy_printer.settings
from mtg_proxy_printer.units_and_sizes import OptStr
from mtg_proxy_printer.logger import get_logger
logger = get_logger(__name__)
del get_logger
|
| ︙ | ︙ | |||
123 124 125 126 127 128 129 |
def __exit__(self, exc_type, exc_val, exc_tb):
self.qt_object.blockSignals(False)
def load_ui_from_file(name: str):
"""
| | < > | > > | 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 |
def __exit__(self, exc_type, exc_val, exc_tb):
self.qt_object.blockSignals(False)
def load_ui_from_file(name: str):
"""
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, _ = 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:
"""Loads a QIcon with the given name from the internal resources"""
file_path = f"{RESOURCE_PATH_PREFIX}/icons/{name}"
if not QFile.exists(file_path):
error_message = f"Icon not found: {file_path}"
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/compiled_resources.pyi.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | # 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/>. """ | | | 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 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/custom_card_import_dialog.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 from pathlib import Path import typing | | | | | 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/>. from collections import Counter from pathlib import Path import typing from PySide6.QtCore import Qt, Signal, Slot from PySide6.QtGui import QDragEnterEvent, QDropEvent, QPixmap from PySide6.QtWidgets import QDialog, QWidget, QFileDialog, QPushButton from mtg_proxy_printer.document_controller import DocumentAction from mtg_proxy_printer.document_controller.import_deck_list import ActionImportDeckList from mtg_proxy_printer.model.card import CustomCard from mtg_proxy_printer.model.document import Document from mtg_proxy_printer.units_and_sizes import CardSizes |
| ︙ | ︙ | |||
43 44 45 46 47 48 49 |
EventTypes = typing.Union[QDragEnterEvent, QDropEvent]
class CustomCardImportDialog(QDialog):
request_action = Signal(DocumentAction)
| | | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
EventTypes = typing.Union[QDragEnterEvent, QDropEvent]
class CustomCardImportDialog(QDialog):
request_action = Signal(DocumentAction)
def __init__(self, document: Document, parent: QWidget = None, flags=Qt.WindowType.Window):
super().__init__(parent, flags)
self.ui = ui = Ui_CustomCardImportDialog()
ui.setupUi(self)
self.ok_button.setEnabled(False)
ui.remove_selected.setDisabled(True)
self.model = model = CardListModel(document)
model.request_action.connect(self.request_action)
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/deck_import_wizard.py.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import itertools import pathlib import re import typing import urllib.error import urllib.parse | | | | > | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import itertools
import pathlib
import re
import typing
import urllib.error
import urllib.parse
from PySide6.QtCore import Slot, Signal, Property, QStringListModel, Qt, SIGNAL, \
QSize, QUrl
from PySide6.QtGui import QValidator, QIcon, QDesktopServices
from PySide6.QtWidgets import QWizard, QFileDialog, QMessageBox, QWizardPage, QWidget, QRadioButton
from mtg_proxy_printer.model.document import Document
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
|
| ︙ | ︙ | |||
112 113 114 115 116 117 118 |
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)
| | | | | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
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)
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", "deck_list_downloader_changed(str)")
self.registerField(
"translate-deck-list-target-language", ui.translate_deck_list_target_language,
"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
|
| ︙ | ︙ | |||
595 596 597 598 599 600 601 |
request_action = Signal(ActionImportDeckList)
BUTTON_ICONS = {
QWizard.WizardButton.FinishButton: "dialog-ok",
QWizard.WizardButton.CancelButton: "dialog-cancel",
}
def __init__(self, document: Document, language_model: QStringListModel,
| | > | 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 |
request_action = Signal(ActionImportDeckList)
BUTTON_ICONS = {
QWizard.WizardButton.FinishButton: "dialog-ok",
QWizard.WizardButton.CancelButton: "dialog-cancel",
}
def __init__(self, document: Document, language_model: QStringListModel,
parent: QWidget = None, flags=Qt.WindowType.Window):
super().__init__(QSize(1000, 600), parent, flags)
self.setDefaultProperty("QPlainTextEdit", "plainText", SIGNAL("textChanged()"))
self.select_deck_parser_page = SelectDeckParserPage(document, self)
self.load_list_page = LoadListPage(language_model, self)
self.summary_page = SummaryPage(document, self)
self.addPage(self.load_list_page)
self.addPage(self.select_deck_parser_page)
self.addPage(self.summary_page)
self.setWindowIcon(QIcon.fromTheme("document-import"))
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/dialogs.py.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | from pathlib import Path import shutil import sys import typing from typing import Tuple | | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | from pathlib import Path import shutil import sys import typing from typing import Tuple from PySide6.QtCore import QFile, Signal, 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 CardDatabase from mtg_proxy_printer.model.card import AnyCardType import mtg_proxy_printer.model.imagedb import mtg_proxy_printer.print import mtg_proxy_printer.settings |
| ︙ | ︙ | |||
303 304 305 306 307 308 309 |
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:
| | | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
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().toStdString()
finally:
file.close()
text_browser.setMarkdown(content)
class PrintPreviewDialog(QPrintPreviewDialog):
|
| ︙ | ︙ | |||
423 424 425 426 427 428 429 |
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."""
| | | 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
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
item.setGraphicsEffect(None)
UNSAFE_FILE_NAME_CHARS = r'''*"/\<>:|?^'''
UNSAFE_FILE_NAME_MAPPING = str.maketrans(UNSAFE_FILE_NAME_CHARS, "_"*len(UNSAFE_FILE_NAME_CHARS))
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/item_delegates.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 itertools import combinations from typing import Union | | | | | 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 typing
from itertools import combinations
from typing import Union
from PySide6.QtCore import QModelIndex, Qt, QAbstractItemModel, QSortFilterProxyModel, QObject, QEvent
from PySide6.QtGui import QKeyEvent, QFocusEvent
from PySide6.QtWidgets import QStyledItemDelegate, QWidget, QStyleOptionViewItem, QComboBox, QSpinBox, QLineEdit, \
QApplication
from mtg_proxy_printer.model.card import MTGSet, Card, AnyCardType
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.logger import get_logger
try:
|
| ︙ | ︙ | |||
112 113 114 115 116 117 118 |
class SetEditorDelegate(FastComboBoxDelegate):
"""
A set editor. For official cards, use a QComboBox with valid set choices for the given card.
For custom cards, use the embedded editor widget to allow free-form text entry.
"""
class CustomCardSetEditor(QWidget):
"""A widget holding two line edits, allowing the user to freely edit the set name & code of custom cards."""
| | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
class SetEditorDelegate(FastComboBoxDelegate):
"""
A set editor. For official cards, use a QComboBox with valid set choices for the given card.
For custom cards, use the embedded editor widget to allow free-form text entry.
"""
class CustomCardSetEditor(QWidget):
"""A widget holding two line edits, allowing the user to freely edit the set name & code of custom cards."""
def __init__(self, parent: QWidget = None, flags=Qt.WindowType.Widget):
super().__init__(parent, flags)
self.ui = ui = Ui_SetEditor()
ui.setupUi(self)
def set_data(self, mtg_set: MTGSet):
self.ui.name_editor.setText(mtg_set.name)
self.ui.code_edit.setText(mtg_set.code)
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/main_window.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import pathlib import typing from functools import partial | > | | | > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import pathlib import typing from functools import partial from PySide6.QtCore import Slot, Signal, QStringListModel, QUrl, Qt from PySide6.QtGui import QCloseEvent, QKeySequence, QAction, QDesktopServices, QDragEnterEvent, QDropEvent 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 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 |
| ︙ | ︙ | |||
139 140 141 142 143 144 145 |
(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):
| < | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
(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.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):
|
| ︙ | ︙ | |||
293 294 295 296 297 298 299 |
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 = dialog = PrintDialog(self.document, self)
dialog.finished.connect(self.on_dialog_finished)
| > | | 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
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 = dialog = PrintDialog(self.document, self)
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(super(QPrintDialog, 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")
|
| ︙ | ︙ | |||
386 387 388 389 390 391 392 |
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:
| | | 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
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.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()
|
| ︙ | ︙ | |||
481 482 483 484 485 486 487 |
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")
| | | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
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.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
|
| ︙ | ︙ | |||
557 558 559 560 561 562 563 |
if mime_data.hasUrls() and len(dropped_urls := mime_data.urls()) == 1:
url = dropped_urls[0].toLocalFile()
path = pathlib.Path(url)
acceptable = path.is_file() and path.suffix.casefold() == f".{DEFAULT_SAVE_SUFFIX}"
if acceptable:
return path
return None
| < < < < < < < < < < < < < < < | 559 560 561 562 563 564 565 |
if mime_data.hasUrls() and len(dropped_urls := mime_data.urls()) == 1:
url = dropped_urls[0].toLocalFile()
path = pathlib.Path(url)
acceptable = path.is_file() and path.suffix.casefold() == f".{DEFAULT_SAVE_SUFFIX}"
if acceptable:
return path
return None
|
Changes to mtg_proxy_printer/ui/page_card_table_view.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import functools import math import operator from pathlib import Path from typing import Union, Optional | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import functools import math import operator from pathlib import Path from typing import Union, Optional from PySide6.QtCore import QPoint, Qt, Signal, Slot, QPersistentModelIndex from PySide6.QtGui import QIcon, QAction from PySide6.QtWidgets import QTableView, QWidget, QMenu, QInputDialog, QFileDialog from mtg_proxy_printer.app_dirs import data_directories from mtg_proxy_printer.document_controller import DocumentAction from mtg_proxy_printer.document_controller.card_actions import ActionAddCard, ActionRemoveCards from mtg_proxy_printer.model.carddb import CardDatabase from mtg_proxy_printer.model.card import Card, CheckCard, CardList, AnyCardType, AnyCardTypeForTypeCheck from mtg_proxy_printer.model.document import Document |
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/page_config_container.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 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.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | | | 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 enum from unittest.mock import MagicMock 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.page_layout 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 |
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/page_config_widget.py.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import functools from functools import partial import math from typing import List, Tuple, Union, Any import pint | | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import functools
from functools import partial
import math
from typing import List, Tuple, Union, Any
import pint
from PySide6.QtCore import Slot, Qt, Signal
from PySide6.QtPrintSupport import QPrinter
from PySide6.QtGui import QPageSize, QPageLayout, QColor
from PySide6.QtWidgets import QGroupBox, QWidget, QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QColorDialog
from mtg_proxy_printer.settings import settings
from mtg_proxy_printer.ui.common import load_ui_from_file, BlockedSignals, highlight_widget
from mtg_proxy_printer.model.page_layout import PageLayoutSettings
from mtg_proxy_printer.units_and_sizes import CardSizes, \
PageType, unit_registry, ConfigParser, PageSizeManager, Unit
|
| ︙ | ︙ | |||
77 78 79 80 81 82 83 |
for page_size_id in PageSizeManager.PageSize.values():
ui.paper_size.addItem(QPageSize.name(page_size_id), page_size_id)
for item, value in PageSizeManager.PageOrientation.items():
ui.paper_orientation.addItem(item, value)
ui.paper_size.currentIndexChanged.connect(self._on_paper_size_changed)
ui.paper_size.currentIndexChanged.connect(self.validate_paper_size_settings)
| | | | | | | | | | > > > | | 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 |
for page_size_id in PageSizeManager.PageSize.values():
ui.paper_size.addItem(QPageSize.name(page_size_id), page_size_id)
for item, value in PageSizeManager.PageOrientation.items():
ui.paper_orientation.addItem(item, value)
ui.paper_size.currentIndexChanged.connect(self._on_paper_size_changed)
ui.paper_size.currentIndexChanged.connect(self.validate_paper_size_settings)
ui.paper_size.currentIndexChanged.connect(self.on_page_layout_changed)
ui.paper_size.currentIndexChanged.connect(lambda: self.page_layout_changed.emit(page_layout))
ui.paper_orientation.currentIndexChanged.connect(self._on_paper_orientation_changed)
ui.paper_orientation.currentIndexChanged.connect(self.validate_paper_size_settings)
ui.paper_orientation.currentIndexChanged.connect(self.on_page_layout_changed)
ui.paper_orientation.currentIndexChanged.connect(lambda: self.page_layout_changed.emit(page_layout))
ui.watermark_opacity.valueChanged.connect(self._on_watermark_color_opacity_changed)
ui.watermark_opacity.valueChanged.connect(lambda: self.page_layout_changed.emit(page_layout))
ui.watermark_color_button.clicked.connect(self._on_watermark_color_button_clicked)
for spinbox, _, unit in self._get_numerical_settings_widgets():
layout_key = spinbox.objectName()
spinbox.valueChanged[float].connect(
partial(self.set_numerical_page_layout_item, page_layout, layout_key, unit))
spinbox.valueChanged[float].connect(self.validate_paper_size_settings)
spinbox.valueChanged[float].connect(self.on_page_layout_changed)
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(lambda: self.page_layout_changed.emit(page_layout))
for line_edit, _ in self._get_string_settings_widgets():
layout_key = line_edit.objectName()
line_edit.textChanged.connect(partial(setattr, page_layout, layout_key))
line_edit.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: Unit, 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)
@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.value)
@Slot(int)
def _on_paper_size_changed(self, index: int):
ui = self.ui
custom_paper_size_selected = not index
ui.paper_orientation.setDisabled(custom_paper_size_selected) # Only valid for predefined paper sizes
ui.custom_page_width.setEnabled(custom_paper_size_selected) # 3 UI elements for custom paper sizes
|
| ︙ | ︙ | |||
158 159 160 161 162 163 164 |
selected = QColorDialog.getColor(self.page_layout.watermark_color)
selected.setAlpha(self.ui.watermark_opacity.value())
self.page_layout.watermark_color = selected
self._show_watermark_color(self.page_layout.watermark_color)
self.page_layout_changed.emit(self.page_layout)
@Slot()
| | | 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
selected = QColorDialog.getColor(self.page_layout.watermark_color)
selected.setAlpha(self.ui.watermark_opacity.value())
self.page_layout.watermark_color = selected
self._show_watermark_color(self.page_layout.watermark_color)
self.page_layout_changed.emit(self.page_layout)
@Slot()
def on_page_layout_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)
oversized_capacity = self.page_layout.compute_page_card_capacity(PageType.OVERSIZED)
regular_text = self.tr(
"%n regular card(s)",
|
| ︙ | ︙ | |||
246 247 248 249 250 251 252 |
for line_edit, setting in self._get_string_settings_widgets():
line_edit.setText(documents_section[setting])
self._show_watermark_color(documents_section.get_color("watermark-color"))
self._load_paper_size(documents_section["paper-size"])
self._load_paper_orientation(documents_section["paper-orientation"])
self.validate_paper_size_settings()
| | | 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
for line_edit, setting in self._get_string_settings_widgets():
line_edit.setText(documents_section[setting])
self._show_watermark_color(documents_section.get_color("watermark-color"))
self._load_paper_size(documents_section["paper-size"])
self._load_paper_orientation(documents_section["paper-orientation"])
self.validate_paper_size_settings()
self.on_page_layout_changed()
self.page_layout_changed.emit(self.page_layout)
logger.debug(f"Loading from settings finished")
def load_from_page_layout(self, other: PageLayoutSettings):
"""Loads the page layout from another PageLayoutSettings instance"""
logger.debug(f"About to load document settings from a document instance")
ui = self.ui
|
| ︙ | ︙ | |||
284 285 286 287 288 289 290 |
self._show_watermark_color(value)
else:
widget.setChecked(value)
setattr(self.page_layout, key, value)
self._load_paper_size(other.paper_size)
self._load_paper_orientation(other.paper_orientation)
self.validate_paper_size_settings()
| | | 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
self._show_watermark_color(value)
else:
widget.setChecked(value)
setattr(self.page_layout, key, value)
self._load_paper_size(other.paper_size)
self._load_paper_orientation(other.paper_orientation)
self.validate_paper_size_settings()
self.on_page_layout_changed()
self.page_layout_changed.emit(self.page_layout)
logger.debug(f"Loading from document settings finished")
def _show_watermark_color(self, color: QColor):
sheet = "QLabel {" + f"background-color: {color.name(QColor.NameFormat.HexArgb)}" + "}"
self.ui.watermark_color.setStyleSheet(sheet)
if self.ui.watermark_opacity.value() != color.alpha():
|
| ︙ | ︙ | |||
369 370 371 372 373 374 375 |
ui = self.ui
widgets_with_settings: List[Tuple[QLineEdit, str]] = [
(ui.document_name, "default-document-name"),
(ui.watermark_text, "watermark-text"),
]
return widgets_with_settings
| | | 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
ui = self.ui
widgets_with_settings: List[Tuple[QLineEdit, str]] = [
(ui.document_name, "default-document-name"),
(ui.watermark_text, "watermark-text"),
]
return widgets_with_settings
def _current_page_size(self) -> QPageSize.PageSizeId:
return self.ui.paper_size.currentData(Qt.ItemDataRole.UserRole)
def _current_page_orientation(self) -> QPageLayout.Orientation:
return self.ui.paper_orientation.currentData(Qt.ItemDataRole.UserRole)
@functools.singledispatchmethod
def highlight_differing_settings(self, to_compare: Union[ConfigParser, PageLayoutSettings]):
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/page_renderer.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import enum import typing from functools import partial | | | | > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import enum import typing from functools import partial 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.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import collections import enum import functools import itertools import typing | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import collections
import enum
import functools
import itertools
import typing
from PySide6.QtCore import Qt, QSizeF, QPointF, QRectF, Signal, QObject, Slot, \
QPersistentModelIndex, QModelIndex, QRect, QPoint, QSize
from PySide6.QtGui import QPen, QColorConstants, QColor, QPalette, QFontMetrics, QPixmap, QTransform, QPolygonF
from PySide6.QtWidgets import QGraphicsItemGroup, QGraphicsItem, QGraphicsPixmapItem, QGraphicsRectItem, \
QGraphicsLineItem, QGraphicsSimpleTextItem, QGraphicsScene, QGraphicsPolygonItem
from mtg_proxy_printer.model.card import CardCorner, AnyCardType
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.document_page import PageColumns
from mtg_proxy_printer.model.page_layout import PageLayoutSettings
from mtg_proxy_printer.settings import settings
|
| ︙ | ︙ | |||
249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
rect.setPos(position)
rect.setPen(self.corner_pen)
rect.setBrush(color)
rect.setOpacity(opaque)
rect.setZValue(RenderLayers.CORNERS.value)
return rect
def on_page_layout_changed(self, new_page_layout: PageLayoutSettings):
for corner in self.corners:
corner.setOpacity(new_page_layout.draw_sharp_corners)
self._update_watermark(self.watermark_item, new_page_layout)
@staticmethod
def _update_watermark(item: QGraphicsSimpleTextItem, page_layout: PageLayoutSettings):
| > | 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
rect.setPos(position)
rect.setPen(self.corner_pen)
rect.setBrush(color)
rect.setOpacity(opaque)
rect.setZValue(RenderLayers.CORNERS.value)
return rect
@Slot(PageLayoutSettings)
def on_page_layout_changed(self, new_page_layout: PageLayoutSettings):
for corner in self.corners:
corner.setOpacity(new_page_layout.draw_sharp_corners)
self._update_watermark(self.watermark_item, new_page_layout)
@staticmethod
def _update_watermark(item: QGraphicsSimpleTextItem, page_layout: PageLayoutSettings):
|
| ︙ | ︙ | |||
600 601 602 603 604 605 606 607 |
def on_rows_inserted(self, parent: QModelIndex, first: int, last: int):
if self._is_valid_page_index(parent) and parent.row() == self.selected_page.row():
inserted_cards = last-first+1
needs_reorder = first + inserted_cards < self.document.rowCount(parent)
next_item = self.card_items[first] if needs_reorder else None
page_type: PageType = self.selected_page.data(ItemDataRole.UserRole)
logger.debug(f"Added {inserted_cards} cards to the currently shown page, drawing them.")
for new in range(first, last+1):
| > | | 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 |
def on_rows_inserted(self, parent: QModelIndex, first: int, last: int):
if self._is_valid_page_index(parent) and parent.row() == self.selected_page.row():
inserted_cards = last-first+1
needs_reorder = first + inserted_cards < self.document.rowCount(parent)
next_item = self.card_items[first] if needs_reorder else None
page_type: PageType = self.selected_page.data(ItemDataRole.UserRole)
logger.debug(f"Added {inserted_cards} cards to the currently shown page, drawing them.")
model = parent.model()
for new in range(first, last+1):
self.draw_card(model.index(new, PageColumns.Image, parent), page_type, next_item)
if needs_reorder:
logger.debug("Cards added in the middle of the page, re-order existing cards.")
self.update_card_positions()
self.update_card_bleeds()
elif not parent.isValid():
# Page inserted. Update the page number text, as it contains the total number of pages
self._update_page_number_text()
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/printing_filter_widgets.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import abc from functools import partial from typing import List, Tuple | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import abc
from functools import partial
from typing import List, Tuple
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.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | # 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/>. | | | | 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/>.
from PySide6.QtCore import Slot, Qt
from PySide6.QtWidgets import QWidget, QLabel, QProgressBar
from mtg_proxy_printer.runner import ProgressSignalContainer
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
|
| ︙ | ︙ | |||
34 35 36 37 38 39 40 |
__all__ = [
"ProgressBar",
]
class ProgressBar(QWidget):
| | | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
__all__ = [
"ProgressBar",
]
class ProgressBar(QWidget):
def __init__(self, parent: QWidget = None, flags=Qt.WindowType.Widget):
super().__init__(parent, flags)
self.ui = ui = Ui_ProgressBar()
ui.setupUi(self)
self.set_outer_progress = ui.outer_progress_bar.setValue
self.set_inner_progress = ui.inner_progress_bar.setValue
self.set_independent_progress = ui.independent_bar.setValue
for item in (ui.inner_progress_bar, ui.inner_progress_label):
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/ui/settings_window.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import pathlib import typing from functools import partial | | | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # along with this program. If not, see <http://www.gnu.org/licenses/>. import pathlib import typing from functools import partial 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.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import json import logging from functools import partial import pathlib import typing from abc import abstractmethod | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import json
import logging
from functools import partial
import pathlib
import typing
from abc import abstractmethod
from PySide6.QtCore import Signal, Slot, QUrl, QStandardPaths, QStringListModel, Qt, QThreadPool
from PySide6.QtGui import QDesktopServices, QStandardItem, QIcon, QColor
from PySide6.QtWidgets import QWidget, QCheckBox, QFileDialog, QMessageBox, QApplication, QLineEdit, QDoubleSpinBox, \
QColorDialog
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, load_file, get_widget_background_color
|
| ︙ | ︙ | |||
71 72 73 74 75 76 77 |
class PageMetadata(typing.NamedTuple):
text: str
icon_name: OptStr
tooltip: OptStr = None
| < | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
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]:
"""Returns a list model item for this page, used to represent the page in the settings page selection UI."""
data = self.display_metadata()
item = QStandardItem(data.text)
|
| ︙ | ︙ | |||
113 114 115 116 117 118 119 |
@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."""
| | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
@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
item.setGraphicsEffect(None)
class DebugSettingsPage(Page):
requested_card_download = Signal(pathlib.Path)
|
| ︙ | ︙ | |||
502 503 504 505 506 507 508 |
self.page_config_widget.highlight_differing_settings(settings)
class PrinterSettingsPage(Page):
def display_metadata(self) -> PageMetadata:
return PageMetadata(self.tr("Printer settings"), "document-print", self.tr("Configure the printer"))
| | | 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 |
self.page_config_widget.highlight_differing_settings(settings)
class PrinterSettingsPage(Page):
def display_metadata(self) -> PageMetadata:
return PageMetadata(self.tr("Printer settings"), "document-print", self.tr("Configure the printer"))
def __init__(self, parent=None, flags=Qt.WindowType.Widget):
super().__init__(parent, flags)
self.ui = ui = Ui_PrinterSettingsPage()
ui.setupUi(self)
def _get_printer_settings_boolean_widgets(self):
ui = self.ui
widgets_with_settings: typing.List[typing.Tuple[QCheckBox, str]] = [
|
| ︙ | ︙ | |||
553 554 555 556 557 558 559 |
highlight_widget(spinbox)
class ExportSettingsPage(Page):
def display_metadata(self) -> PageMetadata:
return PageMetadata(self.tr("Export settings"), "viewpdf", self.tr("Configure the PDF/PNG export"))
| | | 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 |
highlight_widget(spinbox)
class ExportSettingsPage(Page):
def display_metadata(self) -> PageMetadata:
return PageMetadata(self.tr("Export settings"), "viewpdf", self.tr("Configure the PDF/PNG export"))
def __init__(self, parent=None, flags=Qt.WindowType.Widget):
super().__init__(parent, flags)
self.ui = ui = Ui_ExportSettingsPage()
ui.setupUi(self)
self._get_png_background_color = partial(get_widget_background_color, ui.png_background_color)
def load(self, settings: ConfigParser):
ui = self.ui
|
| ︙ | ︙ |
Changes to mtg_proxy_printer/units_and_sizes.py.
| ︙ | ︙ | |||
24 25 26 27 28 29 30 |
from typing import Type, Dict, List, Optional, Set, NamedTuple, TypedDict, Union
try:
from typing import NotRequired
except ImportError: # Compatibility with Python < 3.11
from typing_extensions import NotRequired
| | | < < < | 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 |
from typing import Type, Dict, List, Optional, Set, NamedTuple, TypedDict, Union
try:
from typing import NotRequired
except ImportError: # Compatibility with Python < 3.11
from typing_extensions import NotRequired
from PySide6.QtCore import QSize, QObject
from PySide6.QtGui import QPageSize, QPageLayout, QColor
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
try:
from pint.registry import Unit, Quantity
except ImportError: # Compatibility with Pint 0.21 for Python 3.8 support
Quantity = Unit = typing.Any
import pint
import mtg_proxy_printer.natsort
def _setup_units() -> typing.Tuple[pint.UnitRegistry, QuantityT]:
registry = pint.UnitRegistry(cache_folder=":auto:")
resolution = registry.parse_expression("300dots/inch")
print_context = pint.Context("print")
|
| ︙ | ︙ | |||
352 353 354 355 356 357 358 |
# TODO: Find a better way than this hack that adds 10mm of hard-coded margins.
card_height = CardSizes.OVERSIZED.height.to(mm, "print").magnitude + 10 # Add 10mm for margins
return size.height() >= card_height <= size.width() \
and size.height() >= card_height <= size.width()
def read_page_size_enum() -> Dict[str, PageSizeId]:
| | < < < | < | | | 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# TODO: Find a better way than this hack that adds 10mm of hard-coded margins.
card_height = CardSizes.OVERSIZED.height.to(mm, "print").magnitude + 10 # Add 10mm for margins
return size.height() >= card_height <= size.width() \
and size.height() >= card_height <= size.width()
def read_page_size_enum() -> Dict[str, PageSizeId]:
result = {"Custom": PageSizeId.Custom}
result.update({item.name: item for item in PageSizeId if is_acceptable_page_size(item)})
return result
class PageSizeManager(QObject):
PageSize = read_page_size_enum()
PageSizeReverse = {value: key for key, value in read_page_size_enum().items()}
PageOrientation = {item.name: item for item in QPageLayout.Orientation}
PageOrientationReverse = {item: item.name for item in QPageLayout.Orientation}
|
Changes to mtg_proxy_printer/update_checker.py.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | import socket import sqlite3 import typing import urllib.parse import urllib.error import ijson | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import socket import sqlite3 import typing import urllib.parse import urllib.error import ijson 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, SCHEMA_NAME from mtg_proxy_printer.card_info_downloader import ApiStreamWorker, CardInfoWorkerBase from mtg_proxy_printer.natsort import natural_sorted, str_less_than |
| ︙ | ︙ |
Changes to pyproject.toml.
| ︙ | ︙ | |||
36 37 38 39 40 41 42 |
]
# 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",
| | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
]
# 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",
"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",
|
| ︙ | ︙ | |||
60 61 62 63 64 65 66 |
[project.optional-dependencies]
dev = [
"pytest >= 6.0",
"pytest-cov",
"pytest-timeout",
"pytest-qt >= 2.0",
"tox >= 4.0",
| < < < < | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
[project.optional-dependencies]
dev = [
"pytest >= 6.0",
"pytest-cov",
"pytest-timeout",
"pytest-qt >= 2.0",
"tox >= 4.0",
"pillow; platform_system == 'Windows'", # Used to convert the app icon to .ico
]
package = [
"build",
"cx_Freeze >= 7.2; platform.python_implementation == 'CPython'",
"pillow; platform_system == 'Windows'", # Used to convert the app icon to .ico
]
[project.urls]
Homepage = "https://chiselapp.com/user/luziferius/repository/MTGProxyPrinter/index"
Issues = "https://chiselapp.com/user/luziferius/repository/MTGProxyPrinter/ticket"
|
| ︙ | ︙ | |||
111 112 113 114 115 116 117 |
version = {attr= "mtg_proxy_printer.meta_data.__version__"}
[build-system]
requires = [
"setuptools >= 61",
"wheel >= 0.45",
| | | 107 108 109 110 111 112 113 114 115 116 117 |
version = {attr= "mtg_proxy_printer.meta_data.__version__"}
[build-system]
requires = [
"setuptools >= 61",
"wheel >= 0.45",
"PySide6_Essentials >= 6.7.0",
"build",
]
build-backend = "setuptools.build_meta"
|
Changes to run_tests.bat.
1 2 3 | :: Runs the unit tests :: Create or activate the build environment | | | | 1 2 3 4 5 6 7 8 9 10 | :: Runs the unit tests :: Create or activate the build environment 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 | #!/bin/bash | | | 1 2 3 4 5 | #!/bin/bash source venv-PySide6/bin/activate tox run deactivate |
Changes to scripts/clean_windows_build.bat.
| ︙ | ︙ | |||
9 10 11 12 13 14 15 | :: 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/>. | < < | | < | < | | < > > > | | | | | < | < < | | < < < < < < < < < < < < < < > | < | | < < < < > > | < < < < < < < < < < | | > | < < < < < < | 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/>. if "%1%"=="" ( pushd build\exe* ) else ( pushd "%1" ) pushd lib pushd PySide6 :: Don't need the executables, like Qt6 Designer, etc. :: Delete all typing stubs del *.exe *.pyi :: 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* 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 audio/video codec DLLs del avformat-*.dll avutil-*.dll swresample-*.dll swscale-*.dll :: Unused OpenGL bindings. The Qt6OpenGL DLLs are required and thus not removed del QtOpenGL.pyd QtOpenGLWidgets.pyd opengl32sw.dll pushd translations :: Remove translations for unused/removed components del assistant* designer* linguist* qtdeclarative* :: leave translations popd pushd plugins del /Q /S tls :: leave plugins popd :: leave PySide6 popd del shiboken6\shiboken6*.lib del email\architecture.rst :: leave lib popd ::leave build directory popd |
Changes to scripts/compile_resources.py.
| ︙ | ︙ | |||
70 71 72 73 74 75 76 |
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():
| | < | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
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 = ("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.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 | 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 | < > < < | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
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 itertools
import textwrap
from pathlib import Path
import shutil
import subprocess
from typing import Tuple, NamedTuple, TypeVar, Iterable, Union, Type, List, Any, Dict, Set
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]
|
| ︙ | ︙ | |||
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
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 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)
| > > > > > > | | < | < | < < < < | | < < < < < < < < < < > | < < < > | | 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 |
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)
create_python_package(target_path)
for ui_file in source_path.rglob("*.ui"):
compiled = compile_ui_file(ui_file)
parent_dir = (target_path/ui_file.relative_to(source_path)).parent
create_python_package(parent_dir)
(parent_dir/f"{ui_file.stem}.py").write_text(compiled, "utf-8")
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
create_python_package(parent_dir)
(parent_dir/f"{ui_file.stem}.pyi").write_text(stub, "utf-8")
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:
try:
command = ("pyside6-uic", "--generator", "python", str(path))
except Exception as e:
raise RuntimeError(f"Compilation failed for file {path}") from e
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"
|
| ︙ | ︙ | |||
213 214 215 216 217 218 219 |
return f"class {class_root.name}({base_classes}):"
def get_assignments(function_body: List[ast.stmt]) -> List[Assignment]:
return [
Assignment(
assignment.targets[0].attr,
| | < < < < < < < < < | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
return f"class {class_root.name}({base_classes}):"
def get_assignments(function_body: List[ast.stmt]) -> List[Assignment]:
return [
Assignment(
assignment.targets[0].attr,
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_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.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | """ Management script for application translations """ import argparse | < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | """ Management script for application translations """ import argparse import json import math import pathlib import re import subprocess import sys from typing import Callable, Dict, NamedTuple |
| ︙ | ︙ | |||
93 94 95 96 97 98 99 |
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)
| < < < < < < < < < < < | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
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)
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([
|
| ︙ | ︙ | |||
161 162 163 164 165 166 167 |
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
| < | | | 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
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
lrelease6 = venv / "Lib" / "site-packages" / "PySide6" / "lrelease.exe"
if not lrelease6.is_file():
raise RuntimeError("No fallback lrelease executable found")
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.
| ︙ | ︙ | |||
40 41 42 43 44 45 46 47 48 |
base = "Win32GUI" if sys.platform == "win32" else None
excludes = [
f"{main_package}.resources", # Do not include the raw resources as individual files
"distutils",
"lib2to3",
"pep517",
| > > | > > < < | < | | < | | | < | < | | | < | < < | < | < < | | | < < | > | | | | | | | < | < | 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 |
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",
"pint.testsuite", # Ignore the internal test suite
"pydoc_data",
"pytest",
"sqlite3.test", # Ignore the internal test suite
"tkinter",
"toml",
# Empty package with readme and download scripts
"ctypes.test",
# Unused PySide6 components
"PySide6.glue",
"PySide6.include",
"PySide6.metatypes",
"PySide6.plugins.assetimporters",
"PySide6.plugins.canbus",
"PySide6.plugins.designer",
"PySide6.plugins.geometryloaders",
"PySide6.plugins.geoservices",
"PySide6.plugins.multimedia",
"PySide6.plugins.networkinformation",
"PySide6.plugins.position",
"PySide6.plugins.qmltooling",
"PySide6.plugins.scxmldatamodel",
"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",
"PySide6.typesystems",
]
if sys.platform == "win32":
excludes += [
"platformdirs.android",
"platformdirs.macos",
"platformdirs.unix",
|
| ︙ | ︙ |
Changes to tests/conftest.py.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | fixtures defined here are available in all test modules. """ import itertools import sqlite3 import unittest.mock from pathlib import Path | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | fixtures defined here are available in all test modules. """ import itertools import sqlite3 import unittest.mock from pathlib import Path from PySide6.QtGui import QColorConstants, QPixmap import pytest from hamcrest import assert_that import mtg_proxy_printer.sqlite_helpers import mtg_proxy_printer.settings from mtg_proxy_printer.model.page_layout import PageLayoutSettings from mtg_proxy_printer.printing_filter_updater import PrintingFilterUpdater |
| ︙ | ︙ | |||
56 57 58 59 60 61 62 |
db = mtg_proxy_printer.sqlite_helpers.open_database(":memory:", "document-v7", check_same_thread=False)
if request.param:
db.execute("PRAGMA reverse_unordered_selects = TRUE")
return db
@pytest.fixture
| | < < | 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 |
db = mtg_proxy_printer.sqlite_helpers.open_database(":memory:", "document-v7", check_same_thread=False)
if request.param:
db.execute("PRAGMA reverse_unordered_selects = TRUE")
return db
@pytest.fixture
def image_db(qtbot, tmp_path: Path):
image_db = ImageDatabase(tmp_path)
regular = image_db.get_blank(CardSizes.REGULAR)
large = image_db.get_blank(CardSizes.OVERSIZED)
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] = regular.copy()
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] = large.copy()
image_db.images_on_disk.add(key)
yield image_db
@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
@pytest.fixture
def mock_imagedb():
mock_image_db = unittest.mock.NonCallableMagicMock(spec=ImageDatabase)
blanks = {
CardSizes.REGULAR: QPixmap(CardSizes.REGULAR.as_qsize_px()),
CardSizes.OVERSIZED: QPixmap(CardSizes.OVERSIZED.as_qsize_px()),
|
| ︙ | ︙ | |||
105 106 107 108 109 110 111 |
def document_light(qtbot, mock_imagedb) -> Document:
mock_card_db = unittest.mock.NonCallableMagicMock()
mock_card_db.db = mtg_proxy_printer.sqlite_helpers.create_in_memory_database(
"carddb", check_same_thread=False)
document = Document(mock_card_db, mock_imagedb)
document.loader.db = mock_card_db.db
yield document
| < | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
def document_light(qtbot, mock_imagedb) -> Document:
mock_card_db = unittest.mock.NonCallableMagicMock()
mock_card_db.db = mtg_proxy_printer.sqlite_helpers.create_in_memory_database(
"carddb", check_same_thread=False)
document = Document(mock_card_db, mock_imagedb)
document.loader.db = mock_card_db.db
yield document
@pytest.fixture
def page_layout() -> PageLayoutSettings:
layout = PageLayoutSettings.create_from_settings()
defaults = PageLayoutSettings.create_from_settings(mtg_proxy_printer.settings.DEFAULT_SETTINGS)
assert_that(layout, is_dataclass_equal_to(defaults))
return layout
|
Changes to tests/document_controller/helpers.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | """ This module contains an assortment of small helper functions used in the tests for the document controller """ import itertools | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | """ This module contains an assortment of small helper functions used in the tests for the document controller """ import itertools from PySide6.QtGui import QPixmap import hamcrest.core.base_matcher from hamcrest import has_properties, same_instance, all_of, instance_of, assert_that, is_, equal_to, has_property from mtg_proxy_printer.model.card import MTGSet, Card, AnyCardType from mtg_proxy_printer.model.document_page import CardContainer, Page from mtg_proxy_printer.units_and_sizes import CardSizes, CardSize |
| ︙ | ︙ |
Changes to tests/document_controller/test_action_move_cards.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. from functools import partial from typing import Optional import pytest from hamcrest import * | | | 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 from typing import Optional import pytest from hamcrest import * from PySide6.QtCore import QModelIndex from pytestqt.qtbot import QtBot from mtg_proxy_printer.units_and_sizes import CardSizes, IntList from mtg_proxy_printer.model.document_page import PageType from mtg_proxy_printer.model.document import Document from mtg_proxy_printer.document_controller import IllegalStateError from mtg_proxy_printer.document_controller.page_actions import ActionNewPage |
| ︙ | ︙ |
Changes to tests/document_controller/test_action_remove_page.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 * | | | 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 functools import partial from hamcrest import * from PySide6.QtCore import QModelIndex from pytestqt.qtbot import QtBot from mtg_proxy_printer.model.document_page import Page from mtg_proxy_printer.model.document import Document from mtg_proxy_printer.document_controller import IllegalStateError from mtg_proxy_printer.document_controller.page_actions import ActionRemovePage |
| ︙ | ︙ |
Changes to tests/document_controller/test_action_save_document.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import copy import dataclasses from pathlib import Path import textwrap from hamcrest import * | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import copy import dataclasses from pathlib import Path import textwrap from hamcrest import * from PySide6.QtCore import QModelIndex, Qt import pytest from pytestqt.qtbot import QtBot from mtg_proxy_printer.model.page_layout import PageLayoutSettings from mtg_proxy_printer.sqlite_helpers import open_database, create_in_memory_database from mtg_proxy_printer.units_and_sizes import unit_registry, UnitT from mtg_proxy_printer.model.card import CheckCard |
| ︙ | ︙ |
Changes to tests/model/test_card.py.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | # 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 copy import pytest | | | | 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 copy import pytest from PySide6.QtCore import QBuffer, QIODevice from PySide6.QtGui import QPixmap, QColorConstants, QColor from pytestqt.qtbot import QtBot from mtg_proxy_printer.model.card import Card, MTGSet, CardCorner, CustomCard, CheckCard from mtg_proxy_printer.units_and_sizes import CardSize, CardSizes, PageType, UUID from hamcrest import * |
| ︙ | ︙ |
Changes to tests/model/test_card_list.py.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | from collections import Counter import typing from hamcrest import * import pytest from pytestqt.qtbot import QtBot | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from collections import Counter
import typing
from hamcrest import *
import pytest
from pytestqt.qtbot import QtBot
from PySide6.QtCore import QItemSelectionModel, Qt
from mtg_proxy_printer.document_controller.card_actions import ActionAddCard
from mtg_proxy_printer.model.card import CustomCard, MTGSet
from mtg_proxy_printer.model.carddb import CardIdentificationData
from mtg_proxy_printer.model.card_list import CardListModel, CardListModelRow, CardListColumns, \
CardListToPageColumnMapping
from mtg_proxy_printer.model.document import Document
|
| ︙ | ︙ |
Changes to tests/model/test_carddb.py.
| ︙ | ︙ | |||
197 198 199 200 201 202 203 |
"regular_english_card",
"english_double_faced_card",
"english_double_faced_art_series_card",
"Flowerfoot_Swordmaster_card",
"Flowerfoot_Swordmaster_token",
],
)
| | < | 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
"regular_english_card",
"english_double_faced_card",
"english_double_faced_art_series_card",
"Flowerfoot_Swordmaster_card",
"Flowerfoot_Swordmaster_token",
],
)
return card_db
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/model/test_document.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 import typing import unittest.mock | | | > | 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 copy import typing import unittest.mock 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.units_and_sizes import PageType, unit_registry, UnitT, CardSizes, CardSize |
| ︙ | ︙ | |||
348 349 350 351 352 353 354 |
custom_page_height=300 * mm, custom_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,
paper_size="Custom", paper_orientation="Portrait",
)
document.apply(ActionEditDocumentSettings(custom_layout))
| | < | 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
custom_page_height=300 * mm, custom_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,
paper_size="Custom", paper_orientation="Portrait",
)
document.apply(ActionEditDocumentSettings(custom_layout))
return document
def test_document_reset_clears_modified_page_layout(qtbot: QtBot, page_layout: PageLayoutSettings, document_custom_layout: Document):
assert_that(
document_custom_layout,
has_property("page_layout", not_(equal_to(page_layout)))
)
|
| ︙ | ︙ |
Changes to tests/model/test_document_loader.py.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 | from pathlib import Path import sqlite3 import unittest.mock import textwrap import pint | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from pathlib import Path import sqlite3 import unittest.mock import textwrap import pint from PySide6.QtGui import QColorConstants from pytestqt.qtbot import QtBot import pytest from hamcrest import * import mtg_proxy_printer.model.document_loader from tests.helpers import quantity_close_to from mtg_proxy_printer.units_and_sizes import PageType, unit_registry, UnitT, CardSizes, QuantityT |
| ︙ | ︙ |
Changes to tests/model/test_image_db.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 import pytest | | | | 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 io import pytest from PySide6.QtCore import QBuffer, QIODevice from PySide6.QtGui import QPixmap from hamcrest import * from pytestqt.qtbot import QtBot from mtg_proxy_printer.units_and_sizes import CardSizes, CardSize from mtg_proxy_printer.model.imagedb import ImageDatabase from mtg_proxy_printer.model.imagedb_files import ImageKey |
| ︙ | ︙ |
Changes to tests/test___main__.py.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 |
@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,
| | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
@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) 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()
|
| ︙ | ︙ | |||
78 79 80 81 82 83 84 |
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()
| | | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
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()
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_check_card_rendering.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 * | | | 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 PySide6.QtGui import QPixmap, QColorConstants from mtg_proxy_printer.model.card import MTGSet, Card, CheckCard from mtg_proxy_printer.units_and_sizes import CardSizes @pytest.fixture def blank_image(qtbot) -> QPixmap: |
| ︙ | ︙ |
Changes to tests/test_known_card_image_model.py.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | """ Tests the KnownCardImageModel used internally by the CacheCleanupWizard. """ import pathlib import typing | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | """ Tests the KnownCardImageModel used internally by the CacheCleanupWizard. """ import pathlib import typing 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 |
| ︙ | ︙ | |||
49 50 51 52 53 54 55 |
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.get_blank().save(str(front_image), "PNG")
image_db.get_blank().save(str(back_image), "PNG")
yield Environment(card_db, image_db, front_image, back_image)
| < | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
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.get_blank().save(str(front_image), "PNG")
image_db.get_blank().save(str(back_image), "PNG")
yield Environment(card_db, image_db, front_image, back_image)
@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.
| ︙ | ︙ | |||
22 23 24 25 26 27 28 | import mtg_proxy_printer.settings import mtg_proxy_printer.model.document import mtg_proxy_printer.model.document_loader from mtg_proxy_printer.model.card import CustomCard from mtg_proxy_printer.units_and_sizes import PageType, PageSizeManager, QuantityT, UnitT, unit_registry, StrDict from mtg_proxy_printer.ui.page_scene import RenderMode | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import mtg_proxy_printer.settings import mtg_proxy_printer.model.document import mtg_proxy_printer.model.document_loader from mtg_proxy_printer.model.card import CustomCard from mtg_proxy_printer.units_and_sizes import PageType, PageSizeManager, QuantityT, UnitT, unit_registry, StrDict from mtg_proxy_printer.ui.page_scene import RenderMode from PySide6.QtGui import QPageLayout, QPageSize from PySide6.QtCore import QMarginsF import pytest from hamcrest import * from tests.hasgetter import has_getters from tests.helpers import quantity_close_to, close_to_ PageLayoutSettings = mtg_proxy_printer.model.document_loader.PageLayoutSettings |
| ︙ | ︙ |
Changes to tests/ui/settings/test_card_filter_widgets.py.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 typing
from unittest.mock import patch
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.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | # 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/>. | | | 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/>. 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.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import typing import pytest from hamcrest import * from pytestqt.qtbot import QtBot | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import typing import pytest from hamcrest import * from pytestqt.qtbot import QtBot 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] |
| ︙ | ︙ | |||
43 44 45 46 47 48 49 |
"""
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)
| > > > | > > | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
"""
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))
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.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | | | 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/>. from hamcrest import * import pytest from PySide6.QtCore import QSize from PySide6.QtGui import QColorConstants, QPixmap, QImage, QPainter, QColor from PySide6.QtWidgets import QGraphicsItem, QGraphicsScene from mtg_proxy_printer.model.document_page import PageColumns from mtg_proxy_printer.units_and_sizes import CardSizes 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.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | # 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 pytestqt.qtbot import QtBot | | > | 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/>. from pytestqt.qtbot import QtBot from PySide6.QtCore import Qt from hamcrest import * from mtg_proxy_printer.model.document_page import Page 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. from .test_main_window import main_window # noqa |
| ︙ | ︙ |
Changes to tests/ui/test_deck_import_wizard.py.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | import unittest.mock from unittest.mock import MagicMock from hamcrest import * from pytestqt.qtbot import QtBot import pytest | | | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import unittest.mock
from unittest.mock import MagicMock
from hamcrest import *
from pytestqt.qtbot import QtBot
import pytest
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
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.ui.deck_import_wizard import DeckImportWizard
from mtg_proxy_printer.decklist_parser.re_parsers import MTGOnlineParser, MTGArenaParser, \
GenericRegularExpressionDeckParser
|
| ︙ | ︙ |
Changes to tests/ui/test_item_delegate.py.
| ︙ | ︙ | |||
9 10 11 12 13 14 15 | # 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/>. | | | | 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 PySide6.QtCore import QModelIndex from PySide6.QtWidgets import QSpinBox, QWidget, QStyleOptionViewItem import pytest from hamcrest import * from pytestqt.qtbot import QtBot from mtg_proxy_printer.model.card import MTGSet from mtg_proxy_printer.ui.item_delegates import BoundedCopiesSpinboxDelegate, SetEditorDelegate |
| ︙ | ︙ |
Changes to tests/ui/test_main_window.py.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | from collections import Counter import pathlib import typing import unittest.mock | < | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | from collections import Counter import pathlib import typing import unittest.mock 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.document_controller.save_document import ActionSaveDocument |
| ︙ | ︙ | |||
63 64 65 66 67 68 69 |
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()
| < < < < | 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 |
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()
def test_declining_card_data_update_offer_results_in_no_action(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, "question", return_value=StandardButton.No), \
unittest.mock.patch(
"mtg_proxy_printer.card_info_downloader.DatabaseImportWorker.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()
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()
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.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | # 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/>. | | | 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/>. from PySide6.QtWidgets import QCheckBox, QDoubleSpinBox, QLineEdit import pytest from pytestqt.qtbot import QtBot from hamcrest import * from mtg_proxy_printer.model.page_layout import PageLayoutSettings from mtg_proxy_printer.units_and_sizes import QuantityT, unit_registry |
| ︙ | ︙ |
Changes to tests/ui/test_page_config_dialog.py.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 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.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | # 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 | | | 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 unittest.mock import patch import pint from PySide6.QtWidgets import QDoubleSpinBox, QCheckBox, QLineEdit from hamcrest import * import pytest from pytestqt.qtbot import QtBot from mtg_proxy_printer.model.page_layout import PageLayoutSettings import mtg_proxy_printer.settings |
| ︙ | ︙ |
Changes to tests/ui/test_page_renderer.py.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. from unittest.mock import patch import pytest from hamcrest import * | | | | 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 unittest.mock import patch import pytest from hamcrest import * 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 |
| ︙ | ︙ | |||
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()
| | | 51 52 53 54 55 56 57 58 |
@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)
|
Changes to tests/ui/test_page_scene.py.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 | import typing from unittest.mock import patch from math import ceil from hamcrest import * import pytest | | | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import typing from unittest.mock import patch from math import ceil from hamcrest import * import pytest 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, NeighborsPresent 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.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | # # 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 | | | 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 hamcrest import * import pytest 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"] |
| ︙ | ︙ |