Coverage for lib/lottie/gui/import_export.py: 0%
200 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1import threading
2from functools import reduce
3from PyQt5 import QtCore, QtWidgets, QtGui
4from PyQt5.QtCore import Qt
6from ..exporters.base import exporters
7from ..importers.base import importers
8from ..parsers.baseporter import IoProgressReporter, ExtraOption
11class GuiProgressReporter(IoProgressReporter):
12 def __init__(self):
13 self.dialogs = {}
14 self.threads = {}
15 self.lock = threading.Lock()
16 self.id = 0
18 @classmethod
19 def set_global(cls):
20 IoProgressReporter.instance = cls()
22 def gen_id(self, parent):
23 self.id += 1
24 with self.lock:
25 dialog = QtWidgets.QProgressDialog(parent)
26 dialog.setCancelButton(None)
27 dialog.setWindowModality(Qt.ApplicationModal)
28 self.dialogs[self.id] = dialog
29 return self.id
31 def get_thread_dialog(self):
32 with self.lock:
33 id = threading.current_thread().ident
34 return self.dialogs[self.threads[id]]
36 def setup(self, title, id):
37 with self.lock:
38 tid = threading.current_thread().ident
39 self.threads[tid] = id
40 dialog = self.dialogs[id]
42 dialog.setWindowTitle(title)
43 dialog.hide()
45 def report_progress(self, title, value, total):
46 dialog = self.get_thread_dialog()
47 dialog.show()
48 dialog.setValue(value)
49 dialog.setMaximum(total)
50 dialog.setLabelText(title)
52 def report_message(self, message):
53 dialog = self.get_thread_dialog()
54 dialog.show()
55 dialog.setRange(0, 0)
56 dialog.setLabelText(message)
58 def cleanup(self):
59 with self.lock:
60 id = threading.current_thread().ident
61 dialog = self.dialogs.pop(self.threads.pop(id))
62 dialog.hide()
63 dialog.deleteLater()
66class GuiBasePorter:
67 def __init__(self, porter):
68 self.porter = porter
69 self.file_filter = "%s (%s)" % (porter.name, " ".join("*.%s" % e for e in porter.extensions))
70 self._dialog = None
71 self.widgets = {}
73 def _build_dialog(self):
74 self._dialog = QtWidgets.QDialog()
75 self._dialog.setWindowTitle("Export to %s" % self.porter.name)
76 layout = QtWidgets.QFormLayout()
77 self._dialog.setLayout(layout)
79 for go in self.porter.generic_options:
80 self._generic_option(layout, go)
82 for option in self.porter.extra_options:
83 self._add_option(layout, option)
85 button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
86 button_box.accepted.connect(self._dialog.accept)
87 button_box.rejected.connect(self._dialog.reject)
88 layout.addRow(button_box)
90 def _add_option(self, layout, option: ExtraOption):
91 if option.dest in self.widgets:
92 return
93 label = QtWidgets.QLabel(option.name.replace("_", " ").title())
95 default = option.kwargs.get("default", None)
96 option_type = option.kwargs.get("type", str)
98 if option.kwargs.get("action") == "store_true":
99 widget = QtWidgets.QCheckBox()
100 widget.setChecked(False)
101 getter = widget.isChecked
102 elif option.kwargs.get("action") == "store_false":
103 widget = QtWidgets.QCheckBox()
104 labtext = label.text()
105 if labtext.startswith("No "):
106 label.setText(labtext[3:])
107 widget.setChecked(True)
108 getter = widget.isChecked
109 elif "choices" in option.kwargs:
110 widget = QtWidgets.QComboBox()
111 for choice in option.kwargs["choices"]:
112 widget.addItem(str(choice))
113 if default:
114 widget.setCurrentText(str(default))
115 getter = lambda: option_type(widget.currentText())
116 elif option_type is int:
117 widget = QtWidgets.QSpinBox()
118 widget.setMinimum(-1000)
119 widget.setMaximum(1000)
120 if default is not None:
121 widget.setValue(int(default))
122 getter = widget.value
123 else:
124 widget = QtWidgets.QLineEdit()
125 if default is not None:
126 widget.setText(str(default))
127 getter = widget.text
129 help = option.kwargs.get("help", "")
130 widget.setWhatsThis(help)
131 widget.setToolTip(help)
133 widget.setObjectName(option.dest)
134 self.widgets[option.dest] = getter
135 layout.addRow(label, widget)
137 @property
138 def needs_dialog(self):
139 return self.porter.generic_options or self.porter.extra_options
141 @property
142 def dialog(self):
143 if not self._dialog:
144 self._build_dialog()
145 return self._dialog
147 def get_options(self):
148 return {
149 name: getter()
150 for name, getter in self.widgets.items()
151 }
153 def prompt_options(self, parent):
154 if self.needs_dialog:
155 self.dialog.setParent(parent)
156 self.dialog.setWindowFlags(Qt.Dialog)
157 if self.dialog.exec_() != QtWidgets.QDialog.DialogCode.Accepted:
158 return None
160 return self.get_options()
162 def _generic_option(self, layout, go):
163 pass
166class GuiExporter(GuiBasePorter):
167 def _generic_option(self, layout, go):
168 if go == "pretty":
169 self._add_option(layout, ExtraOption("pretty", action="store_true", help="Pretty print"))
170 if go == "frame":
171 self._add_option(layout, ExtraOption("frame", type=int, help="Frame to extract"))
173 @property
174 def exporter(self):
175 return self.porter
178class GuiImporter(GuiBasePorter):
179 @property
180 def importer(self):
181 return self.porter
184class ExportThread(QtCore.QThread):
185 def __init__(self, parent, exporter, animation, file_name, options):
186 super().__init__()
187 self.id = IoProgressReporter.instance.gen_id(parent)
188 self.animation = animation
189 self.exporter = exporter
190 self.file_name = file_name
191 self.options = options
193 def run(self):
194 IoProgressReporter.instance.setup("Export to %s" % self.exporter.name, self.id)
195 try:
196 self.exporter.process(self.animation, self.file_name, **self.options)
197 except Exception:
198 IoProgressReporter.instance.report_message("Error on export")
199 IoProgressReporter.instance.cleanup()
202gui_exporters = list(map(GuiExporter, exporters))
203gui_importers = list(map(GuiImporter, importers))
206def get_open_filename(parent, title, dirname):
207 extensions = reduce(lambda a, b: a | b, (set(gi.importer.extensions) for gi in gui_importers))
208 all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions)
209 filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_importers)
210 file_name, filter = QtWidgets.QFileDialog.getOpenFileName(
211 parent, title, dirname, filters
212 )
213 if file_name:
214 if filter == all_files:
215 importer = gui_importer_from_filename(file_name)
216 if importer:
217 return file_name, importer
218 else:
219 for importer in gui_importers:
220 if importer.file_filter == filter:
221 return file_name, importer
223 return None, None
226def get_save_filename(parent, title, dirname):
227 extensions = reduce(lambda a, b: a | b, (set(gi.exporter.extensions) for gi in gui_exporters))
228 all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions)
229 filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_exporters)
230 file_name, filter = QtWidgets.QFileDialog.getSaveFileName(
231 parent, title, dirname, filters
232 )
234 if file_name:
235 if filter == all_files:
236 exporter = gui_exporter_from_filename(file_name)
237 if exporter:
238 return file_name, exporter
239 else:
240 for exporter in gui_exporters:
241 if exporter.file_filter == filter:
242 return file_name, exporter
244 return None, None
247def start_export(parent, exporter, animation, file_name, options):
248 thread = ExportThread(parent, exporter, animation, file_name, options)
249 thread.start()
250 return thread
253def gui_importer_from_filename(filename):
254 importer = importers.get_from_filename(filename)
255 for gip in gui_importers:
256 if gip.importer is importer:
257 return gip
258 return None
261def gui_exporter_from_filename(filename):
262 exporter = exporters.get_from_filename(filename)
263 for gip in gui_exporters:
264 if gip.exporter is exporter:
265 return gip
266 return None