diff --git a/nion/swift/DocumentController.py b/nion/swift/DocumentController.py index d40365f42..c5d4ed2a8 100755 --- a/nion/swift/DocumentController.py +++ b/nion/swift/DocumentController.py @@ -888,9 +888,9 @@ def export_file(self, display_item: DisplayItem.DisplayItem) -> None: def export_files(self, display_items: typing.Sequence[DisplayItem.DisplayItem]) -> None: if len(display_items) > 1: - export_dialog = ExportDialog.ExportDialog(self.ui, self) - export_dialog.on_accept = functools.partial(export_dialog.do_export, display_items) - export_dialog.show() + export_dialog = ExportDialog.ExportDialog(self.ui, self, self, display_items) + # export_dialog.on_accept = functools.partial(export_dialog.do_export, display_items) + # export_dialog.show() elif len(display_items) == 1: self.export_file(display_items[0]) diff --git a/nion/swift/ExportDialog.py b/nion/swift/ExportDialog.py index 0f24810dc..ad4e356c7 100644 --- a/nion/swift/ExportDialog.py +++ b/nion/swift/ExportDialog.py @@ -1,6 +1,7 @@ from __future__ import annotations # standard libraries +from dataclasses import dataclass import functools import gettext import logging @@ -34,167 +35,189 @@ _ = gettext.gettext -class ExportDialog(Dialog.OkCancelDialog): - def __init__(self, ui: UserInterface.UserInterface, parent_window: Window.Window): - super().__init__(ui, ok_title=_("Export"), parent_window=parent_window) +@dataclass(init=False) +class ExportDialogViewModel: + include_title: Model.PropertyModel() + include_date: Model.PropertyModel() + include_dimensions: Model.PropertyModel() + include_sequence: Model.PropertyModel() + include_prefix: Model.PropertyModel() + prefix: Model.PropertyModel() + directory: Model.PropertyModel() + writer: Model.PropertyModel() + + def __init__(self, title: bool, date: bool, dimensions: bool, sequence: bool, prefix: str = None, directory: str = None, writer=None): + self.include_title = Model.PropertyModel(title) + self.include_date = Model.PropertyModel(date) + self.include_dimensions = Model.PropertyModel(dimensions) + self.include_sequence = Model.PropertyModel(sequence) + if prefix: + self.include_prefix = Model.PropertyModel(True) + self.prefix = Model.PropertyModel(prefix) + else: + self.include_prefix = Model.PropertyModel(False) + self.prefix = Model.PropertyModel(None) + self.directory = Model.PropertyModel(directory) + self.writer = Model.PropertyModel(writer) - io_handler_id = self.ui.get_persistent_string("export_io_handler_id", "png-io-handler") +class ExportDialog(): + def __init__(self, ui: UserInterface.UserInterface, parent_window: Window.Window, document_controller: DocumentController.DocumentController, display_items: DisplayItem.DisplayItems): + super().__init__() + + self.ui = ui + io_handler_id = self.ui.get_persistent_string("export_io_handler_id", "png-io-handler") + self.__document_controller = document_controller self.directory = self.ui.get_persistent_string("export_directory", self.ui.get_document_location()) self.writer = ImportExportManager.ImportExportManager().get_writer_by_id(io_handler_id) - directory_column = self.ui.create_column_widget() - - title_row = self.ui.create_row_widget() - title_row.add_spacing(13) - title_row.add(self.ui.create_label_widget(_("Export Folder: "), properties={"font": "bold"})) - title_row.add_stretch() - title_row.add_spacing(13) - - show_directory_row = self.ui.create_row_widget() - show_directory_row.add_spacing(26) - directory_label = self.ui.create_label_widget(self.directory) - show_directory_row.add(directory_label) - show_directory_row.add_stretch() - show_directory_row.add_spacing(13) - - choose_directory_row = self.ui.create_row_widget() - choose_directory_row.add_spacing(26) - choose_directory_button = self.ui.create_push_button_widget(_("Choose...")) - choose_directory_row.add(choose_directory_button) - choose_directory_row.add_stretch() - choose_directory_row.add_spacing(13) - - directory_column.add(title_row) - directory_column.add(show_directory_row) - directory_column.add(choose_directory_row) - - file_types_column = self.ui.create_column_widget() - - title_row = self.ui.create_row_widget() - title_row.add_spacing(13) - title_row.add(self.ui.create_label_widget(_("File Type: "), properties={"font": "bold"})) - title_row.add_stretch() - title_row.add_spacing(13) - - file_types_row = self.ui.create_row_widget() - file_types_row.add_spacing(26) - writers = ImportExportManager.ImportExportManager().get_writers() - file_types_combo_box = self.ui.create_combo_box_widget(items=writers, item_getter=operator.attrgetter("name")) - file_types_combo_box.current_item = self.writer - file_types_row.add(file_types_combo_box) - file_types_row.add_stretch() - file_types_row.add_spacing(13) - - file_types_column.add(title_row) - file_types_column.add(file_types_row) - - option_descriptions = [ - (_("Include Title"), "title", True), - (_("Include Date"), "date", True), - (_("Include Dimensions"), "dimensions", True), - (_("Include Sequence Number"), "sequence", True), - (_("Include Prefix:"), "prefix", False) - ] - - self.options = dict() - - options_column = self.ui.create_column_widget() - - title_row = self.ui.create_row_widget() - title_row.add_spacing(13) - title_row.add(self.ui.create_label_widget(_("Filename: "), properties={"font": "bold"})) - title_row.add_stretch() - title_row.add_spacing(13) - - individual_options_column = self.ui.create_column_widget() - for option_decription in option_descriptions: - label, option_id, default_value = option_decription - self.options[option_id] = self.ui.get_persistent_string("export_option_" + option_id, - str(default_value)).lower() == "true" - check_box_widget = self.ui.create_check_box_widget(label) - check_box_widget.checked = self.options[option_id] - - def checked_changed(option_id_: str, checked: bool) -> None: - self.options[option_id_] = checked - self.ui.set_persistent_string("export_option_" + option_id_, str(checked)) - - check_box_widget.on_checked_changed = functools.partial(checked_changed, option_id) - individual_options_column.add_spacing(4) - individual_options_column.add(check_box_widget) - if option_id == "prefix": - self.prefix_edit_widget = self.ui.create_text_edit_widget(properties={"max-height": 35}) - individual_options_column.add(self.prefix_edit_widget) - - options_row = self.ui.create_row_widget() - options_row.add_spacing(26) - options_row.add(individual_options_column) - options_row.add_stretch() - options_row.add_spacing(13) - - options_column.add(title_row) - options_column.add(options_row) - - column = self.ui.create_column_widget() - column.add_spacing(12) - column.add(directory_column) - column.add_spacing(4) - column.add(options_column) - column.add_spacing(4) - column.add(file_types_column) - column.add_spacing(16) - column.add_stretch() - - def choose() -> None: - existing_directory, directory = self.ui.get_existing_directory_dialog(_("Choose Export Directory"), - self.directory) - if existing_directory: - self.directory = existing_directory - directory_label.text = self.directory - self.ui.set_persistent_string("export_directory", self.directory) - - choose_directory_button.on_clicked = choose - - def writer_changed(writer: ImportExportManager.ImportExportHandler) -> None: - self.ui.set_persistent_string("export_io_handler_id", writer.io_handler_id) - self.writer = writer - - file_types_combo_box.on_current_item_changed = writer_changed - - self.content.add(column) - - def do_export(self, display_items: typing.Sequence[DisplayItem.DisplayItem]) -> None: - directory = self.directory - writer = self.writer + self.viewmodel = ExportDialogViewModel(True, True, True, True, None, self.directory, self.writer) + u = Declarative.DeclarativeUI() + self._build_ui(u) + + dialog = typing.cast(Dialog.ActionDialog, + Declarative.construct(document_controller.ui, document_controller, u.create_modeless_dialog(self.ui_view, title=_("Export")), self)) + dialog.add_button(_("Cancel"), self.cancel) + dialog.add_button(_("Export"), functools.partial(self.export_clicked, display_items, self.viewmodel)) + dialog.show() + + def choose_directory(self, widget: Declarative.UIWidget) -> None: + existing_directory, directory = self.ui.get_existing_directory_dialog(_("Choose Export Directory"), + self.directory) + if existing_directory: + self.directory = existing_directory + self.viewmodel.directory.value = self.directory + self.ui.set_persistent_string("export_directory", self.directory) + + def on_writer_changed(self, widget: Declarative.UIWidget, current_index: int) -> None: + writer = self.writers[current_index] + self.viewmodel.writer.value = writer + + def _build_ui(self, u: Declarative.DeclarativeUI): + print("Building UI") + self.file_type_index = 0 + self.writers = ImportExportManager.ImportExportManager().get_writers() + writers_names = [getattr(writer, "name") for writer in self.writers] + self.file_types = writers_names + + # self.viewmodel = viewmodel + + # Export Folder + directory_label = u.create_row(u.create_label(text="Location:")) + directory_text = u.create_row(u.create_label(text=f'@binding(viewmodel.directory.value)')) + self.directory_text_label = directory_text + directory_button = u.create_row(u.create_push_button(text="Select Path..", on_clicked='choose_directory')) + + # Filename + filename_label = u.create_row(u.create_label(text="Filename:")) + ## Title + title_checkbox = u.create_row(u.create_check_box(text="Include Title", checked=f'@binding(viewmodel.include_title.value)')) + + ## Date + date_checkbox = u.create_row(u.create_check_box(text="Include Date", checked=f'@binding(viewmodel.include_date.value)')) + + ## Dimensions + dimension_checkbox = u.create_row(u.create_check_box(text="Include Dimensions", checked=f'@binding(viewmodel.include_dimensions.value)')) + + ## Sequence Number + sequence_checkbox = u.create_row(u.create_check_box(text="Include Sequence Number", checked=f'@binding(viewmodel.include_sequence.value)')) + + ## Prefix + prefix_checkbox = u.create_row(u.create_check_box(text="Include Prefix", checked=f'@binding(viewmodel.include_prefix.value)')) + prefix_textbox = u.create_row(u.create_text_edit(text=f'@binding(viewmodel.prefix.value)')) + + ## File Type + file_type_combobox = u.create_combo_box(items=self.file_types, current_index=f'@binding(file_type_index.value)', on_current_index_changed="on_writer_changed") + + ## Build final ui column + column = u.create_column(directory_label, + directory_text, + directory_button, + filename_label, + title_checkbox, + date_checkbox, + dimension_checkbox, + sequence_checkbox, + prefix_checkbox, + prefix_textbox, + file_type_combobox, + spacing=12, margin=12) + self.ui_view = column + + @staticmethod + def build_filename(components: typing.List[str], extension: str, suffix: str = "($)", path: str = None) -> str: + # if path doesn't end in a directory character, add one + if path: + if not (path.endswith('/') or path.endswith('\\')): + path = path + '/' + + # if extension doesn't start with a '.', add one so we always know it is there + if not extension.startswith('.'): + extension = '.' + extension + + # stick components together for the first part of the filename, underscore delimited excluding blank component + filename = "_".join(s for s in components if s) + + # check to see if filename is available, if so return that + test_filename = filename + extension + if path: + test_filename = path + test_filename + + if not os.path.exists(test_filename): + return test_filename + + # file must already exist + if suffix and '$' in suffix: + found_available = False + next_index = 1 + max_index = 999 + while not found_available and next_index <= max_index: + test_suffix = suffix.replace("$", str(next_index)) + test_filename = filename + test_suffix + extension + if path: + test_filename = path + test_filename + if not os.path.exists(test_filename): + return test_filename + next_index = next_index + 1 + + # Well we have no option here but to just go with the overwrite, either we ran out of index options or had none to begin with + return test_filename + + @staticmethod + def export_clicked(display_items: typing.Sequence[DisplayItem.DisplayItem], viewmodel: ExportDialogViewModel) -> bool: + directory = viewmodel.directory.value + writer = viewmodel.writer if directory and writer: for index, display_item in enumerate(display_items): data_item = display_item.data_item if data_item: try: components = list() - if self.options.get("prefix", False): - components.append(str(self.prefix_edit_widget.text)) - if self.options.get("title", False): + if viewmodel.include_prefix.value: # self.options.get("prefix", False): + components.append(str(viewmodel.prefix.value)) # prefix_edit_widget.text)) + if viewmodel.include_title.value: title = unicodedata.normalize('NFKC', data_item.title) title = re.sub(r'[^\w\s-]', '', title, flags=re.U).strip() title = re.sub(r'[-\s]+', '-', title, flags=re.U) components.append(title) - if self.options.get("date", False): + if viewmodel.include_date.value: components.append(data_item.created_local.isoformat().replace(':', '')) - if self.options.get("dimensions", False): + if viewmodel.include_dimensions.value: components.append( "x".join([str(shape_n) for shape_n in data_item.dimensional_shape])) - if self.options.get("sequence", False): + if viewmodel.include_sequence.value: components.append(str(index)) - filename = "_".join(components) - extension = writer.extensions[0] - path = os.path.join(directory, "{0}.{1}".format(filename, extension)) - ImportExportManager.ImportExportManager().write_display_item_with_writer(writer, display_item, pathlib.Path(path)) + filename = ExportDialog.build_filename(components, writer.value.extensions[0], path=directory) + ImportExportManager.ImportExportManager().write_display_item_with_writer(writer.value, display_item, pathlib.Path(filename)) except Exception as e: logging.debug("Could not export image %s / %s", str(data_item), str(e)) traceback.print_exc() traceback.print_stack() + return True + + def cancel(self) -> bool: + return True class ExportSVGHandler: def __init__(self, display_item: DisplayItem.DisplayItem, display_size: Geometry.IntSize) -> None: