Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch png_export Excluding Merge-Ins
This is equivalent to a diff from 63a062bb69 to 3029546d95
2025-05-15
| ||
11:35 | Implemented exporting documents as a sequence of PNG images. Implements [2423d3adb8ca5a3a] check-in: 2d74fab949 user: thomas tags: trunk | |
09:43 | Implement progress reporting for PNG sequence exports. Closed-Leaf check-in: 3029546d95 user: thomas tags: png_export | |
2025-05-08
| ||
11:41 | Create new branch named "static_cardb_fixture" check-in: cd3715f19f user: thomas tags: static_cardb_fixture | |
2025-05-07
| ||
15:39 | Merge with trunk check-in: 1caa0e5983 user: thomas tags: port_pyside6 | |
15:35 | Sync with trunk. check-in: 7e276d3b1f user: thomas tags: png_export | |
15:34 | Cleaned up development environment creation scripts and the build environment requirements. tox and pip-tools aren't required in the environment used to build the wheel file. check-in: 63a062bb69 user: thomas tags: trunk | |
10:23 | Add multiple user experience improvements check-in: 836b4bdf29 user: thomas tags: trunk | |
Changes to doc/changelog.md.
1 2 3 4 5 6 7 8 9 10 11 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | + + + + | # Changelog # Next version (in development) ## New features - Export documents as a lossless PNG image sequence. The export can be triggered via the File menu. ## Changed features - The main window now opens maximized when starting the application. Added a setting to restore the previous behavior. - Added option to open wizards and dialogs maximized. Off by default. - Reduced click count required for switching printings from 5 to 3. Now, double-clicking editable table cells automatically opens the list with choices. Clicking an entry in the list saves immediately. |
︙ |
Changes to mtg_proxy_printer/print.py.
︙ | |||
11 12 13 14 15 16 17 18 19 | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | # 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 functools import partial try: 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 |
︙ |
Changes to mtg_proxy_printer/print_count_updater.py.
︙ | |||
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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 | + + - + | which can take multiple minutes if the network connection is sufficiently slow. If it were synchronous, printing would block the UI thread until the update completes, or the app would miss writing the data at all, or printing/PDF export had to be prohibited. """ def __init__(self, document: "Document", db: sqlite3.Connection = None): super().__init__() self.db_path = document.card_db.db_path # Collect the data now, so that the delayed run() does not operate on a potentially modified document, # but can use the data from the time the document was printed/exported. self.data = document.get_all_card_keys_in_document() self.db_passed_in = bool(db) self._db = db @property def db(self) -> sqlite3.Connection: # Delay connection creation until first access. # Avoids opening connections that aren't actually used and opens the connection # in the thread that actually uses it. if self._db is None: logger.debug(f"{self.__class__.__name__}.db: Opening new database connection") self._db = open_database(self.db_path, SCHEMA_NAME) return self._db def run(self): """ Increments the usage count of all cards used in the document and updates the last use timestamps. |
︙ |
Added mtg_proxy_printer/resources/icons/breeze/actions/16/document-export.svg.
|
Added mtg_proxy_printer/resources/icons/breeze/actions/22/document-export.svg.
|
Added mtg_proxy_printer/resources/icons/breeze/actions/24/document-export.svg.
|
Added mtg_proxy_printer/resources/icons/breeze/actions/32/document-export.svg.
|
Changes to mtg_proxy_printer/resources/resources.qrc.
︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | + | <!-- actions --> <!-- 16x16 --> <file>icons/breeze/actions/16/application-exit.svg</file> <file>icons/breeze/actions/16/configure.svg</file> <file>icons/breeze/actions/16/dialog-cancel.svg</file> <file>icons/breeze/actions/16/dialog-ok.svg</file> <file>icons/breeze/actions/16/document-close.svg</file> <file>icons/breeze/actions/16/document-export.svg</file> <file>icons/breeze/actions/16/document-import.svg</file> <file>icons/breeze/actions/16/document-new.svg</file> <file>icons/breeze/actions/16/document-open.svg</file> <file>icons/breeze/actions/16/document-print-direct.svg</file> <file>icons/breeze/actions/16/document-print-preview.svg</file> <file>icons/breeze/actions/16/document-print.svg</file> <file>icons/breeze/actions/16/document-properties.svg</file> |
︙ | |||
64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | + | <!-- 22x22 --> <file>icons/breeze/actions/22/application-exit.svg</file> <file>icons/breeze/actions/22/configure.svg</file> <file>icons/breeze/actions/22/dialog-cancel.svg</file> <file>icons/breeze/actions/22/dialog-ok.svg</file> <file>icons/breeze/actions/22/document-close.svg</file> <file>icons/breeze/actions/22/document-export.svg</file> <file>icons/breeze/actions/22/document-import.svg</file> <file>icons/breeze/actions/22/document-new.svg</file> <file>icons/breeze/actions/22/document-open.svg</file> <file>icons/breeze/actions/22/document-print-direct.svg</file> <file>icons/breeze/actions/22/document-print-preview.svg</file> <file>icons/breeze/actions/22/document-print.svg</file> <file>icons/breeze/actions/22/document-properties.svg</file> |
︙ | |||
97 98 99 100 101 102 103 104 105 106 107 108 109 110 | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | + | <!-- 24x24 --> <file>icons/breeze/actions/24/application-exit.svg</file> <file>icons/breeze/actions/24/configure.svg</file> <file>icons/breeze/actions/24/dialog-cancel.svg</file> <file>icons/breeze/actions/24/dialog-ok.svg</file> <file>icons/breeze/actions/24/document-close.svg</file> <file>icons/breeze/actions/24/document-export.svg</file> <file>icons/breeze/actions/24/document-import.svg</file> <file>icons/breeze/actions/24/document-new.svg</file> <file>icons/breeze/actions/24/document-open.svg</file> <file>icons/breeze/actions/24/document-print-direct.svg</file> <file>icons/breeze/actions/24/document-print-preview.svg</file> <file>icons/breeze/actions/24/document-print.svg</file> <file>icons/breeze/actions/24/document-properties.svg</file> |
︙ | |||
130 131 132 133 134 135 136 137 138 139 140 141 142 143 | 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | + | <!-- 32x32, not all icons are available at this resolution! --> <file>icons/breeze/actions/32/application-exit.svg</file> <file>icons/breeze/actions/32/configure.svg</file> <file>icons/breeze/actions/32/dialog-cancel.svg</file> <file>icons/breeze/actions/32/dialog-ok.svg</file> <file>icons/breeze/actions/32/document-close.svg</file> <file>icons/breeze/actions/32/document-export.svg</file> <file>icons/breeze/actions/32/document-import.svg</file> <file>icons/breeze/actions/32/document-new.svg</file> <file>icons/breeze/actions/32/document-open.svg</file> <file>icons/breeze/actions/32/document-print-direct.svg</file> <file>icons/breeze/actions/32/document-print.svg</file> <file>icons/breeze/actions/32/document-properties.svg</file> <file>icons/breeze/actions/32/document-replace.svg</file> |
︙ |
Changes to mtg_proxy_printer/resources/ui/main_window.ui.
︙ | |||
33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | + | <addaction name="action_new_document"/> <addaction name="action_load_document"/> <addaction name="action_save_document"/> <addaction name="action_save_as"/> <addaction name="action_print_preview"/> <addaction name="action_print"/> <addaction name="action_print_pdf"/> <addaction name="action_export_png"/> <addaction name="separator"/> <addaction name="action_import_deck_list"/> <addaction name="action_add_custom_cards"/> <addaction name="separator"/> <addaction name="action_quit"/> </widget> <widget class="QMenu" name="menu_settings"> |
︙ | |||
362 363 364 365 366 367 368 369 370 371 372 373 374 375 | 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 | + + + + + + + + + + + | <action name="action_add_custom_cards"> <property name="icon"> <iconset theme="list-add"/> </property> <property name="text"> <string>Add custom cards</string> </property> </action> <action name="action_export_png"> <property name="icon"> <iconset theme="document-export"/> </property> <property name="text"> <string>Create image sequence</string> </property> <property name="toolTip"> <string>Export document as an image sequence</string> </property> </action> </widget> <customwidgets> <customwidget> <class>CentralWidget</class> <extends>QWidget</extends> <header>mtg_proxy_printer.ui.central_widget</header> |
︙ |
Changes to mtg_proxy_printer/ui/dialogs.py.
︙ | |||
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 | 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 | + + + | from mtg_proxy_printer.model.carddb import CardDatabase import mtg_proxy_printer.model.document import mtg_proxy_printer.model.imagedb import mtg_proxy_printer.print import mtg_proxy_printer.settings import mtg_proxy_printer.ui.common import mtg_proxy_printer.meta_data if typing.TYPE_CHECKING: from mtg_proxy_printer.ui.main_window import MainWindow from mtg_proxy_printer.units_and_sizes import DEFAULT_SAVE_SUFFIX, ConfigParser from mtg_proxy_printer.document_controller.edit_document_settings import ActionEditDocumentSettings from mtg_proxy_printer.print_count_updater import PrintCountUpdater from mtg_proxy_printer.logger import get_logger try: from mtg_proxy_printer.ui.generated.about_dialog import Ui_AboutDialog from mtg_proxy_printer.ui.generated.document_settings_dialog import Ui_DocumentSettingsDialog except ModuleNotFoundError: from mtg_proxy_printer.ui.common import load_ui_from_file Ui_AboutDialog = load_ui_from_file("about_dialog") Ui_DocumentSettingsDialog = load_ui_from_file("document_settings_dialog") EventType = QEvent.Type logger = get_logger(__name__) del get_logger __all__ = [ "SavePDFDialog", "SavePNGDialog", "SaveDocumentAsDialog", "LoadDocumentDialog", "AboutDialog", "PrintPreviewDialog", "PrintDialog", "DocumentSettingsDialog", ] |
︙ | |||
108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | QThreadPool.globalInstance().start(PrintCountUpdater(self.document)) logger.info(f"Saved document to {path}") @Slot() def on_reject(self): logger.debug("User aborted saving to PDF. Doing nothing.") class SavePNGDialog(QFileDialog): def __init__(self, parent: "MainWindow", document: mtg_proxy_printer.model.document.Document): # Note: Cannot supply already translated strings to __init__, # because tr() requires to have returned from super().__init__() super().__init__(parent, "", self.get_preferred_file_name(document)) self.setWindowTitle(self.tr("Export as PNG")) self.setNameFilter(self.tr("PNG images (*.png)")) if default_path := read_path("pdf-export", "pdf-export-path"): self.setDirectory(default_path) self.document = document self.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) self.setDefaultSuffix("png") self.setFileMode(QFileDialog.FileMode.AnyFile) self.accepted.connect(self.on_accept) self.rejected.connect(self.on_reject) logger.info(f"Created {self.__class__.__name__} instance.") @staticmethod def get_preferred_file_name(document: mtg_proxy_printer.model.document.Document): if document.save_file_path is None: return "" # Note: Qt automatically appends the preferred file extension (.png), if the file does not have one. # So ensure it ends on ".png", if there is a dot in the name. Otherwise, let the user enter the name without # pre-setting an extension for a cleaner dialog stem = document.save_file_path.stem return f"{stem}.png" if "." in stem else stem @Slot() def on_accept(self): logger.debug("User chose a file name, about to generate the PNG image sequence") path = self.selectedFiles()[0] main_window: "MainWindow" = self.parent() renderer = mtg_proxy_printer.print.PNGRenderer(main_window, self.document, path) main_window.progress_bars.connect_outer_progress(renderer) renderer.render_document() QThreadPool.globalInstance().start(PrintCountUpdater(self.document)) logger.info(f"Saved document to {path}") @Slot() def on_reject(self): logger.debug("User aborted exporting to PNG. Doing nothing.") class LoadSaveDialog(QFileDialog): def __init__(self, *args, **kwargs): # Note: Cannot supply already translated strings to __init__, # because tr() requires to have returned from super().__init__() super().__init__(*args, **kwargs) filter_text = self.tr( |
︙ |
Changes to mtg_proxy_printer/ui/main_window.py.
︙ | |||
32 33 34 35 36 37 38 | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | - + | from mtg_proxy_printer.document_controller.new_document import ActionNewDocument from mtg_proxy_printer.document_controller.card_actions import ActionAddCard from mtg_proxy_printer.ui.custom_card_import_dialog import CustomCardImportDialog from mtg_proxy_printer.units_and_sizes import DEFAULT_SAVE_SUFFIX import mtg_proxy_printer.settings import mtg_proxy_printer.print from mtg_proxy_printer.ui.dialogs import SavePDFDialog, SaveDocumentAsDialog, LoadDocumentDialog, \ |
︙ | |||
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | + + + | ui.action_add_empty_card, ui.action_discard_page, ui.central_widget, ui.action_cleanup_local_image_cache, ui.action_print, ui.action_print_pdf, ui.action_print_preview, ui.action_export_png, ui.action_show_settings, ui.action_add_custom_cards, ui.action_download_missing_card_images, ] def _connect_image_database_signals(self, image_db: ImageDatabase): image_db.card_download_starting.connect(self.progress_bars.begin_inner_progress) image_db.card_download_finished.connect(self.progress_bars.end_inner_progress) image_db.card_download_progress.connect(self.progress_bars.set_inner_progress) image_db.batch_process_starting.connect(self.progress_bars.begin_outer_progress) |
︙ | |||
300 301 302 303 304 305 306 307 308 309 310 311 312 313 | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | + + + + + + + + + + + + | "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 = SavePDFDialog(self, self.document) dialog.finished.connect(self.on_dialog_finished) self.missing_images_manager.obtain_missing_images(dialog.open) @Slot() def on_action_export_png_triggered(self): logger.info(f"User exports the current document to a sequence of PNG images.") action_str = self.tr( "exporting as a PNG image sequence", "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 = SavePNGDialog(self, self.document) dialog.finished.connect(self.on_dialog_finished) self.missing_images_manager.obtain_missing_images(dialog.open) @Slot() def on_action_add_empty_card_triggered(self): empty_card = self.document.get_empty_card_for_current_page() action = ActionAddCard(empty_card) self.document.apply(action) def on_network_error_occurred(self, message: str): |
︙ |
Changes to mtg_proxy_printer/ui/progress_bar.py.
︙ | |||
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 | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + | # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from PyQt5.QtCore import pyqtSlot as Slot, Qt from PyQt5.QtWidgets import QWidget, QLabel, QProgressBar from 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 Ui_ProgressBar = load_ui_from_file("progress_bar") from mtg_proxy_printer.logger import get_logger logger = get_logger(__name__) del get_logger ConnectionType = Qt.ConnectionType QueuedConnection = ConnectionType.QueuedConnection __all__ = [ "ProgressBar", ] class ProgressBar(QWidget): def __init__(self, parent: QWidget = None, flags=Qt.WindowType()): 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): self._set_retain_size_policy(item, True) item.hide() for item in (ui.outer_progress_bar, ui.outer_progress_label, ui.independent_bar, ui.independent_label): item.hide() |
︙ |