File indexing completed on 2025-09-21 04:03:13

0001 import threading
0002 from functools import reduce
0003 from PyQt5 import QtCore, QtWidgets, QtGui
0004 from PyQt5.QtCore import Qt
0005 
0006 from ..exporters.base import exporters
0007 from ..importers.base import importers
0008 from ..parsers.baseporter import IoProgressReporter, ExtraOption
0009 
0010 
0011 class GuiProgressReporter(IoProgressReporter):
0012     def __init__(self):
0013         self.dialogs = {}
0014         self.threads = {}
0015         self.lock = threading.Lock()
0016         self.id = 0
0017 
0018     @classmethod
0019     def set_global(cls):
0020         IoProgressReporter.instance = cls()
0021 
0022     def gen_id(self, parent):
0023         self.id += 1
0024         with self.lock:
0025             dialog = QtWidgets.QProgressDialog(parent)
0026             dialog.setCancelButton(None)
0027             dialog.setWindowModality(Qt.ApplicationModal)
0028             self.dialogs[self.id] = dialog
0029         return self.id
0030 
0031     def get_thread_dialog(self):
0032         with self.lock:
0033             id = threading.current_thread().ident
0034             return self.dialogs[self.threads[id]]
0035 
0036     def setup(self, title, id):
0037         with self.lock:
0038             tid = threading.current_thread().ident
0039             self.threads[tid] = id
0040             dialog = self.dialogs[id]
0041 
0042         dialog.setWindowTitle(title)
0043         dialog.hide()
0044 
0045     def report_progress(self, title, value, total):
0046         dialog = self.get_thread_dialog()
0047         dialog.show()
0048         dialog.setValue(value)
0049         dialog.setMaximum(total)
0050         dialog.setLabelText(title)
0051 
0052     def report_message(self, message):
0053         dialog = self.get_thread_dialog()
0054         dialog.show()
0055         dialog.setRange(0, 0)
0056         dialog.setLabelText(message)
0057 
0058     def cleanup(self):
0059         with self.lock:
0060             id = threading.current_thread().ident
0061             dialog = self.dialogs.pop(self.threads.pop(id))
0062             dialog.hide()
0063             dialog.deleteLater()
0064 
0065 
0066 class GuiBasePorter:
0067     def __init__(self, porter):
0068         self.porter = porter
0069         self.file_filter = "%s (%s)" % (porter.name, " ".join("*.%s" % e for e in porter.extensions))
0070         self._dialog = None
0071         self.widgets = {}
0072 
0073     def _build_dialog(self):
0074         self._dialog = QtWidgets.QDialog()
0075         self._dialog.setWindowTitle("Export to %s" % self.porter.name)
0076         layout = QtWidgets.QFormLayout()
0077         self._dialog.setLayout(layout)
0078 
0079         for go in self.porter.generic_options:
0080             self._generic_option(layout, go)
0081 
0082         for option in self.porter.extra_options:
0083             self._add_option(layout, option)
0084 
0085         button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
0086         button_box.accepted.connect(self._dialog.accept)
0087         button_box.rejected.connect(self._dialog.reject)
0088         layout.addRow(button_box)
0089 
0090     def _add_option(self, layout, option: ExtraOption):
0091         if option.dest in self.widgets:
0092             return
0093         label = QtWidgets.QLabel(option.name.replace("_", " ").title())
0094 
0095         default = option.kwargs.get("default", None)
0096         option_type = option.kwargs.get("type", str)
0097 
0098         if option.kwargs.get("action") == "store_true":
0099             widget = QtWidgets.QCheckBox()
0100             widget.setChecked(False)
0101             getter = widget.isChecked
0102         elif option.kwargs.get("action") == "store_false":
0103             widget = QtWidgets.QCheckBox()
0104             labtext = label.text()
0105             if labtext.startswith("No "):
0106                 label.setText(labtext[3:])
0107             widget.setChecked(True)
0108             getter = widget.isChecked
0109         elif "choices" in option.kwargs:
0110             widget = QtWidgets.QComboBox()
0111             for choice in option.kwargs["choices"]:
0112                 widget.addItem(str(choice))
0113             if default:
0114                 widget.setCurrentText(str(default))
0115             getter = lambda: option_type(widget.currentText())
0116         elif option_type is int:
0117             widget = QtWidgets.QSpinBox()
0118             widget.setMinimum(-1000)
0119             widget.setMaximum(1000)
0120             if default is not None:
0121                 widget.setValue(int(default))
0122             getter = widget.value
0123         else:
0124             widget = QtWidgets.QLineEdit()
0125             if default is not None:
0126                 widget.setText(str(default))
0127             getter = widget.text
0128 
0129         help = option.kwargs.get("help", "")
0130         widget.setWhatsThis(help)
0131         widget.setToolTip(help)
0132 
0133         widget.setObjectName(option.dest)
0134         self.widgets[option.dest] = getter
0135         layout.addRow(label, widget)
0136 
0137     @property
0138     def needs_dialog(self):
0139         return self.porter.generic_options or self.porter.extra_options
0140 
0141     @property
0142     def dialog(self):
0143         if not self._dialog:
0144             self._build_dialog()
0145         return self._dialog
0146 
0147     def get_options(self):
0148         return {
0149             name: getter()
0150             for name, getter in self.widgets.items()
0151         }
0152 
0153     def prompt_options(self, parent):
0154         if self.needs_dialog:
0155             self.dialog.setParent(parent)
0156             self.dialog.setWindowFlags(Qt.Dialog)
0157             if self.dialog.exec_() != QtWidgets.QDialog.DialogCode.Accepted:
0158                 return None
0159 
0160         return self.get_options()
0161 
0162     def _generic_option(self, layout, go):
0163         pass
0164 
0165 
0166 class GuiExporter(GuiBasePorter):
0167     def _generic_option(self, layout, go):
0168         if go == "pretty":
0169             self._add_option(layout, ExtraOption("pretty", action="store_true", help="Pretty print"))
0170         if go == "frame":
0171             self._add_option(layout, ExtraOption("frame", type=int, help="Frame to extract"))
0172 
0173     @property
0174     def exporter(self):
0175         return self.porter
0176 
0177 
0178 class GuiImporter(GuiBasePorter):
0179     @property
0180     def importer(self):
0181         return self.porter
0182 
0183 
0184 class ExportThread(QtCore.QThread):
0185     def __init__(self, parent, exporter, animation, file_name, options):
0186         super().__init__()
0187         self.id = IoProgressReporter.instance.gen_id(parent)
0188         self.animation = animation
0189         self.exporter = exporter
0190         self.file_name = file_name
0191         self.options = options
0192 
0193     def run(self):
0194         IoProgressReporter.instance.setup("Export to %s" % self.exporter.name, self.id)
0195         try:
0196             self.exporter.process(self.animation, self.file_name, **self.options)
0197         except Exception:
0198             IoProgressReporter.instance.report_message("Error on export")
0199         IoProgressReporter.instance.cleanup()
0200 
0201 
0202 gui_exporters = list(map(GuiExporter, exporters))
0203 gui_importers = list(map(GuiImporter, importers))
0204 
0205 
0206 def get_open_filename(parent, title, dirname):
0207     extensions = reduce(lambda a, b: a | b, (set(gi.importer.extensions) for gi in gui_importers))
0208     all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions)
0209     filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_importers)
0210     file_name, filter = QtWidgets.QFileDialog.getOpenFileName(
0211         parent, title, dirname, filters
0212     )
0213     if file_name:
0214         if filter == all_files:
0215             importer = gui_importer_from_filename(file_name)
0216             if importer:
0217                 return file_name, importer
0218         else:
0219             for importer in gui_importers:
0220                 if importer.file_filter == filter:
0221                     return file_name, importer
0222 
0223     return None, None
0224 
0225 
0226 def get_save_filename(parent, title, dirname):
0227     extensions = reduce(lambda a, b: a | b, (set(gi.exporter.extensions) for gi in gui_exporters))
0228     all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions)
0229     filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_exporters)
0230     file_name, filter = QtWidgets.QFileDialog.getSaveFileName(
0231         parent, title, dirname, filters
0232     )
0233 
0234     if file_name:
0235         if filter == all_files:
0236             exporter = gui_exporter_from_filename(file_name)
0237             if exporter:
0238                 return file_name, exporter
0239         else:
0240             for exporter in gui_exporters:
0241                 if exporter.file_filter == filter:
0242                     return file_name, exporter
0243 
0244     return None, None
0245 
0246 
0247 def start_export(parent, exporter, animation, file_name, options):
0248     thread = ExportThread(parent, exporter, animation, file_name, options)
0249     thread.start()
0250     return thread
0251 
0252 
0253 def gui_importer_from_filename(filename):
0254     importer = importers.get_from_filename(filename)
0255     for gip in gui_importers:
0256         if gip.importer is importer:
0257             return gip
0258     return None
0259 
0260 
0261 def gui_exporter_from_filename(filename):
0262     exporter = exporters.get_from_filename(filename)
0263     for gip in gui_exporters:
0264         if gip.exporter is exporter:
0265             return gip
0266     return None