Skip to content

Commit

Permalink
Refactored Export Dialog in Declarative Mode
Browse files Browse the repository at this point in the history
Fixed the bug with no checkboxes led to duplicate filenames, stopping all duplicate names happening.
Reimplemented in Declarative UI model.
Extracted the settings to a dataclass that can be re-used for scripting.
Extracted the export functionality to a static function that can be re-used for scripting.
  • Loading branch information
Tiomat85 committed May 23, 2024
1 parent fdac61c commit 17960f6
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 140 deletions.
6 changes: 3 additions & 3 deletions nion/swift/DocumentController.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
297 changes: 160 additions & 137 deletions nion/swift/ExportDialog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

# standard libraries
from dataclasses import dataclass
import functools
import gettext
import logging
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 17960f6

Please sign in to comment.