MTGProxyPrinter

Changes On Branch png_export
Login

Changes On Branch png_export

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
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
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

from PyQt5.QtCore import QObject, QMarginsF, QSizeF, pyqtSlot as Slot, QPersistentModelIndex
from PyQt5.QtGui import QPainter, QPdfWriter, QPageSize
from PyQt5.QtCore import QObject, QMarginsF, QSizeF, pyqtSlot as Slot, QPersistentModelIndex, QThreadPool
from PyQt5.QtGui import QPainter, QPdfWriter, QPageSize, QImage
from PyQt5.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
import mtg_proxy_printer.meta_data
from mtg_proxy_printer.settings import settings
from mtg_proxy_printer.model.document import Document
from mtg_proxy_printer.model.page_layout import PageLayoutSettings
from mtg_proxy_printer.model.carddb import with_database_write_lock
from mtg_proxy_printer.ui.page_scene import RenderMode, PageScene
from mtg_proxy_printer.logger import get_logger
import mtg_proxy_printer.units_and_sizes
logger = get_logger(__name__)
del get_logger

RenderHint = QPainter.RenderHint

__all__ = [
    "export_pdf",
    "create_printer",
    "Renderer",
    "PNGRenderer",
]

PNGEncoderThreadLimit = BoundedSemaphore(max(1, process_cpu_count()-1))


class PNGRenderer(ProgressSignalContainer):
    def __init__(self, main_window: "MainWindow", document: Document, file_path: str):
        super().__init__(main_window)
        self.document = document
        self.file_path = Path(file_path)
        self.page_count = document.rowCount()
        self.completed = 0

    def render_document(self):
        document = self.document
        file_path = self.file_path
        page_count = self.page_count
        if not page_count:  # No pages in document
            logger.error("Tried to export a document with zero pages. Aborting.")
            self.update_completed.emit()
            return
        logger.info(f'Exporting document with {document.rowCount()} pages as PNG image sequence to "{file_path}"')
        page_size = document.page_layout.to_page_layout(RenderMode.ON_PAPER).pageSize().sizePixels(
            round(RESOLUTION.magnitude))
        pool = QThreadPool.globalInstance()
        scene = PageScene(document, RenderMode.ON_PAPER, self)
        number_width = len(str(page_count))
        parent = file_path.parent
        self.begin_update.emit(page_count, self.tr("Export as PNGs"))
        for page_nr in range(page_count):
            file_name = f"{file_path.stem}-{str(page_nr + 1).zfill(number_width)}.png"
            output_path = parent / file_name
            image = QImage(page_size, QImage.Format.Format_RGB888)
            painter = QPainter(image)
            scene.on_current_page_changed(document.index(page_nr, 0))
            scene.render(painter)
            pool.start(partial(self._compress_single_image, image, output_path))

    @with_database_write_lock(PNGEncoderThreadLimit)
    def _compress_single_image(self, image: QImage, output_path: Path):
        image.save(str(output_path), "PNG", 0)
        self.completed += 1
        self.advance_progress.emit()
        self.progress.emit(self.completed)
        if self.completed == self.page_count:
            self.update_completed.emit()


def export_pdf(document: Document, file_path: str, parent: QObject = None):
    pages_to_print = settings["pdf-export"].getint("pdf-page-count-limit") or document.rowCount()
    if not pages_to_print:  # No pages in document. Return now, to avoid dividing by zero
        logger.error("Tried to export a document with zero pages as a PDF. Aborting.")
        return
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
64

65
66
67
68
69
70
71
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.
        Should be called after a successful PDF export and direct printing.
        Should be called after a successful PDF/PNG export and direct printing.
        """
        try:
            self._update_image_usage()
        finally:
            self.release_instance()

    @with_database_write_lock()
Added mtg_proxy_printer/resources/icons/breeze/actions/16/document-export.svg.











1
2
3
4
5
6
7
8
9
10
11
+
+
+
+
+
+
+
+
+
+
+
<!DOCTYPE svg>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <style type="text/css" id="current-color-scheme">
            .ColorScheme-Text {
                color:#232629;
            }
        </style>
    </defs>
    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 7 12 L 11.0859 12 L 9.46484 13.6211 L 10.1719 14.3281 L 13 11.5 L 10.1719 8.67187 L 9.46484 9.37891 L 11.0859 11 L 7 11 L 7 12 Z M 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 9 L 13 9 L 13 5 L 10 2 L 3 2 L 3 14 L 8 14 L 8 13 L 4 13 Z"/>
</svg>
Added mtg_proxy_printer/resources/icons/breeze/actions/22/document-export.svg.











1
2
3
4
5
6
7
8
9
10
11
+
+
+
+
+
+
+
+
+
+
+
<!DOCTYPE svg>
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <style type="text/css" id="current-color-scheme">
            .ColorScheme-Text {
                color:#232629;
            }
        </style>
    </defs>
    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 11 16 L 16.293 16 L 14 18.293 L 14.707 19 L 18.207 15.5 L 14.707 12 L 14 12.707 L 16.293 15 L 11 15 L 11 16 Z M 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 13 L 18 13 L 18 7 L 14 3 L 4 3 L 4 19 L 13 19 L 13 18 L 5 18 Z"/>
</svg>
Added mtg_proxy_printer/resources/icons/breeze/actions/24/document-export.svg.













1
2
3
4
5
6
7
8
9
10
11
12
13
+
+
+
+
+
+
+
+
+
+
+
+
+
<!DOCTYPE svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" version="1.1" width="24" height="24">
  <defs>
    <style type="text/css" id="current-color-scheme">
            .ColorScheme-Text {
                color:#232629;
            }
        </style>
  </defs>
  <g transform="translate(1,1)">
    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 11 16 L 16.293 16 L 14 18.293 L 14.707 19 L 18.207 15.5 L 14.707 12 L 14 12.707 L 16.293 15 L 11 15 L 11 16 Z M 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 13 L 18 13 L 18 7 L 14 3 L 4 3 L 4 19 L 13 19 L 13 18 L 5 18 Z"/>
  </g>
</svg>
Added mtg_proxy_printer/resources/icons/breeze/actions/32/document-export.svg.











1
2
3
4
5
6
7
8
9
10
11
+
+
+
+
+
+
+
+
+
+
+
<!DOCTYPE svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <defs>
        <style type="text/css" id="current-color-scheme">
            .ColorScheme-Text {
                color:#232629;
            }
        </style>
    </defs>
    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 7 5 L 18 5 L 18 12 L 25 12 L 25 19 L 26 19 L 26 11 L 19 4 L 6 4 L 6 28 L 19 28 L 19 27 L 7 27 L 7 5 Z M 16.9961 22.5 L 16.9961 23.5 L 25.0859 23.5 L 21.293 27.293 L 22 28 L 27 23 L 22 18 L 21.293 18.707 L 25.0859 22.5 L 16.9961 22.5 Z"/>
</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
39

40
41
42
43
44
45
46
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, \
    AboutDialog, PrintPreviewDialog, PrintDialog, DocumentSettingsDialog
    AboutDialog, PrintPreviewDialog, PrintDialog, DocumentSettingsDialog, SavePNGDialog
from mtg_proxy_printer.ui.common import show_wizard_or_dialog
from mtg_proxy_printer.ui.cache_cleanup_wizard import CacheCleanupWizard
from mtg_proxy_printer.ui.deck_import_wizard import DeckImportWizard
from mtg_proxy_printer.ui.progress_bar import ProgressBar

try:
    from mtg_proxy_printer.ui.generated.main_window import Ui_MainWindow
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
49


























50
51
52
53
54
55
56
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()
        

    def connect_inner_progress(self, sender: ProgressSignalContainer, con_type: ConnectionType = QueuedConnection):
        self._connect_progress_slots(
            sender, con_type,
            self.begin_inner_progress, self.set_inner_progress, self.end_inner_progress
        )

    def connect_outer_progress(self, sender: ProgressSignalContainer, con_type: ConnectionType = QueuedConnection):
        self._connect_progress_slots(
            sender, con_type,
            self.begin_outer_progress, self.set_outer_progress, self.end_outer_progress
        )

    def connect_independent_progress(self, sender: ProgressSignalContainer, con_type: ConnectionType = QueuedConnection):
        self._connect_progress_slots(
            sender, con_type,
            self.begin_independent_progress, self.set_independent_progress, self.end_independent_progress
        )

    @staticmethod
    def _connect_progress_slots(
            sender: ProgressSignalContainer, con_type: ConnectionType, begin_slot, progress_slot, end_slot):
        sender.begin_update.connect(begin_slot, con_type)
        sender.progress.connect(progress_slot, con_type)
        sender.update_completed.connect(end_slot, con_type)

    @staticmethod
    def _set_retain_size_policy(widget: QWidget, value: bool):
        policy = widget.sizePolicy()
        policy.setRetainSizeWhenHidden(value)
        widget.setSizePolicy(policy)

    @Slot(int)