From 265392ddb686bb257ae9cca3640bc743498b2b4d Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 15:26:12 -0800 Subject: [PATCH 1/8] Add a programatic setText method --- sept_qt/input_widget.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sept_qt/input_widget.py b/sept_qt/input_widget.py index d5cf03e..e07bd7d 100644 --- a/sept_qt/input_widget.py +++ b/sept_qt/input_widget.py @@ -179,6 +179,11 @@ def __display_error(): return __display_error + def setText(self, text): + self.blockSignals(True) + self._line_widget.setHtml(text) + self.blockSignals(False) + @QtCore.Slot(object) def recieve_error(self, error): """ From 410ca250c54d1ddfb61d74ed1aeb3b7fc21411fc Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 15:48:02 -0800 Subject: [PATCH 2/8] We actually want to have signals fire --- sept_qt/input_widget.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sept_qt/input_widget.py b/sept_qt/input_widget.py index e07bd7d..4b7b964 100644 --- a/sept_qt/input_widget.py +++ b/sept_qt/input_widget.py @@ -180,9 +180,8 @@ def __display_error(): return __display_error def setText(self, text): - self.blockSignals(True) self._line_widget.setHtml(text) - self.blockSignals(False) + self._handle_text_edited() @QtCore.Slot(object) def recieve_error(self, error): From 02abb38456a09da44cc452b0a45152456dce17f1 Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 15:51:50 -0800 Subject: [PATCH 3/8] Add a manual refresh button --- sept_qt/input_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sept_qt/input_widget.py b/sept_qt/input_widget.py index 4b7b964..fdaf94b 100644 --- a/sept_qt/input_widget.py +++ b/sept_qt/input_widget.py @@ -183,6 +183,9 @@ def setText(self, text): self._line_widget.setHtml(text) self._handle_text_edited() + def refresh(self): + self._line_widget.setHtml(self._line_widget.toPlainText()) + @QtCore.Slot(object) def recieve_error(self, error): """ From 59ce52b74de296904557e6683349b235a81d3760 Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 16:20:39 -0800 Subject: [PATCH 4/8] Refactor the TemplateInputWidget _build_ui method to allow for overriding We will be overriding the _line_widget with a more complex container in our child class --- sept_qt/input_widget.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sept_qt/input_widget.py b/sept_qt/input_widget.py index fdaf94b..3c4d58d 100644 --- a/sept_qt/input_widget.py +++ b/sept_qt/input_widget.py @@ -66,6 +66,16 @@ def __init__(self, parser, error_colour=None, timeout=None, parent=None): def _build_ui(self): self.setLayout(QtWidgets.QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) + _line_widget = self._build_input_widget() + + self._error_widget = QtWidgets.QLabel(self) + self._error_widget.setWordWrap(True) + self._error_widget.hide() + + self.layout().addWidget(_line_widget) + self.layout().addWidget(self._error_widget) + + def _build_input_widget(self): self._line_widget = QtWidgets.QTextEdit(self) self._line_widget.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) self._line_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) @@ -76,15 +86,8 @@ def _build_ui(self): QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed, ) - - self._error_widget = QtWidgets.QLabel(self) - self._error_widget.setWordWrap(True) - self._error_widget.hide() - - self.layout().addWidget(self._line_widget) - self.layout().addWidget(self._error_widget) - self._line_widget.textChanged.connect(self._handle_text_edited) + return self._line_widget def _stop_error_timer(self): """ From b56b882a3eb2eb2107420d8fcaaadd22b3fe061b Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 16:22:34 -0800 Subject: [PATCH 5/8] Build a InputWidget that can load from disk This widget inherits from TemplateInputWidget and adds a load and save button to the ui widget. It adds a new construction parameter "disk_path" that allows you to set the default template_str from disk. It also implements a public `load_path` method to load from a filepath. --- sept_qt/file_input_widget.py | 178 +++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 sept_qt/file_input_widget.py diff --git a/sept_qt/file_input_widget.py b/sept_qt/file_input_widget.py new file mode 100644 index 0000000..0610b0e --- /dev/null +++ b/sept_qt/file_input_widget.py @@ -0,0 +1,178 @@ +import os + +from Qt import QtGui, QtWidgets, QtCore + +from sept import errors + +from .input_widget import TemplateInputWidget + + +class FileTemplateInputWidget(TemplateInputWidget): + """ + FileTemplateInputWidget extends the TemplateInputWidget in allowing users + to interactively create `sept.Template` objects by allowing you to + load and save your template strings to disk. + + All that TemplateInputWidget needs is a valid `sept.PathTemplateParser` + instance, however for styling in your GUI application, you can + optionally pass an `error_colour` that will drive the highlighting + colour when errors occur. + + *Automatically Load From Disk* + You may want to automatically populate your FileTemplateInputWidget with + data that persists on disk. + This can be done by passing the path to your serialized `sept.Template` + file into the `disk_path` parameter. + + *Interactivity Timeouts* + You may also wish to define how long the timeout occurs between the last + character input and our error highlighting occurs. + By default this is set at 1250ms but you can override this by passing a + value to `timeout` during instantiation. + + *Error handling* + When using this in a larger GUI application, you may want to have a + centralized place for displaying errors, if that is the case, you can + send errors to the `recieve_error` slot. + However, to ensure it visualizes correctly, you will want to ensure your + error class has "location" and "length" attributes on it that can be + used to display the highlighting. + """ + LOAD_TEXT = "Load SEPT template" + SAVE_TEXT = "Save SEPT template" + + def __init__(self, parser, error_colour=None, timeout=None, disk_path=None, parent=None): + super(FileTemplateInputWidget, self).__init__( + parser=parser, + error_colour=error_colour, + timeout=timeout, + parent=parent + ) + self._load_from_disk_button = None + self._save_to_disk_button = None + self._disk_path = disk_path + + if disk_path and os.path.exists(disk_path): + self.load_path(disk_path) + + def load_path(self, path): + """ + load_path will attempt to read and validate your `sept.Template` from + the filepath passed in. + If you errors occur while validating, a popup will handle any exceptions + from validating the template. + + :param str path: Path to a file on disk containing the template_str. + """ + try: + template_str = self._read_from_path(path) + except errors.SeptError as err: + import traceback + message = "Error loading template data from {path}\n" \ + "Error was: {error}\n" \ + "{long_error}"\ + .format( + path=path, + error=str(err), + long_error=traceback.format_exc() + ) + self._display_error( + message=message, + title="Error loading template data from disk!" + ) + return + else: # No errors + self.setText(template_str) + + def _build_input_widget(self): + widget = QtWidgets.QWidget(self) + widget.setLayout(QtWidgets.QHBoxLayout()) + widget.layout().setSpacing(0) + widget.layout().setContentsMargins(0, 0, 0, 0) + + line_edit_widget = super(FileTemplateInputWidget, self)._build_input_widget() + self._load_from_disk_button = QtWidgets.QPushButton("...") + self._load_from_disk_button.setMaximumWidth(24) + self._load_from_disk_button.clicked.connect( + self._handle_load_disk_button_clicked) + self._save_to_disk_button = QtWidgets.QPushButton("Save...") + self._save_to_disk_button.setMaximumWidth(56) + self._save_to_disk_button.clicked.connect( + self._handle_save_disk_button_clicked) + + widget.layout().addWidget(line_edit_widget) + widget.layout().addWidget(self._load_from_disk_button) + widget.layout().addWidget(self._save_to_disk_button) + return widget + + def _display_error(self, message, title="Error!"): + QtWidgets.QMessageBox.critical(self, title, message) + + def _display_information(self, message, title="Information!"): + QtWidgets.QMessageBox.information(self, title, message) + + def _read_from_path(self, path): + if not os.path.exists(path): + return + with open(path, "r") as fh: + data = fh.read() + try: + self.parser.validate_template(data) + except errors.SeptError: + raise + return data + + def _get_folder_path(self): + path = os.getcwd() + path_is_dir = True + if os.path.isfile(self._disk_path): + if os.path.exists(self._disk_path): + path = self._disk_path + path_is_dir = False + elif os.path.exists(os.path.dirname(self._disk_path)): + path = os.path.dirname(self._disk_path) + + elif os.path.isdir(self._disk_path): + if os.path.exists(self._disk_path): + path = self._disk_path + return path, path_is_dir + + @QtCore.Slot() + def _handle_save_disk_button_clicked(self): + if not self.template: + self._display_information( + message="Input text box does not contain a valid template. Please fix any errors or type one.", + title="No valid template" + ) + return + path, path_is_dir = self._get_folder_path() + new_path, _ = QtWidgets.QFileDialog.getSaveFileName( + self, + self.SAVE_TEXT, + path + ) + with open(new_path, "w") as fh: + fh.write(self.template._template_str) + self._display_information( + message="Template saved successfully to {}".format(new_path), + title="Success!" + ) + + @QtCore.Slot() + def _handle_load_disk_button_clicked(self): + path, path_is_dir = self._get_folder_path() + + if path_is_dir: + new_path = QtWidgets.QFileDialog.getExistingDirectory( + self, + self.LOAD_TEXT, + path + ) + else: + new_path, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + self.LOAD_TEXT, + path + ) + self._disk_path = new_path + self.load_path(self._disk_path) From 98de3ae1862d80fed32046fd71c61cbfb7f96fd8 Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 16:22:53 -0800 Subject: [PATCH 6/8] Include our newly created FileTemplateInputWidget --- sept_qt/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sept_qt/__init__.py b/sept_qt/__init__.py index 351e799..9f1f5e0 100644 --- a/sept_qt/__init__.py +++ b/sept_qt/__init__.py @@ -1,6 +1,7 @@ from .documentation_widget import DocumentationWidget from .input_widget import TemplateInputWidget from .preview_widget import TemplatePreviewWidget +from .file_input_widget import FileTemplateInputWidget from ._version import get_versions From 2e624dc4679f525c425591dbc7afbff9352ff015 Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Wed, 24 Feb 2021 16:24:07 -0800 Subject: [PATCH 7/8] Implement usage of new FileTemplateInputWidget --- usage.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/usage.py b/usage.py index ec10a21..eb57ec7 100644 --- a/usage.py +++ b/usage.py @@ -4,7 +4,7 @@ from PySide import QtGui from sept import PathTemplateParser, Token -from sept_qt import TemplateInputWidget, TemplatePreviewWidget, DocumentationWidget +from sept_qt import TemplatePreviewWidget, DocumentationWidget, FileTemplateInputWidget def get_tokens(): @@ -240,7 +240,11 @@ def _build_ui(self): self.main_page = QtGui.QWidget() self.main_page.setLayout(QtGui.QVBoxLayout()) - self.template_input_widget = TemplateInputWidget(self.parser) + template_path = os.path.join(os.path.dirname(__file__), "template.sept") + if not os.path.exists(template_path): + with open(template_path, "w") as fh: + fh.write("{{sequence}}/{{shot}}_v{{version}}.{{extension}}") + self.template_input_widget = FileTemplateInputWidget(self.parser, disk_path=template_path, parent=self) self.template_preview_widget = TemplatePreviewWidget(self.version_data) self.template_input_widget.template_changed.connect( self.template_preview_widget.preview_template @@ -249,6 +253,9 @@ def _build_ui(self): self.template_input_widget.recieve_error ) + # Refresh based off our disk loaded template + self.template_input_widget.refresh() + self.main_page.layout().addWidget(self.template_input_widget) self.main_page.layout().addWidget(self.template_preview_widget) From b0f4cd1ccd39f478fceed5756792166a54410198 Mon Sep 17 00:00:00 2001 From: Black Code Formatter Date: Thu, 25 Feb 2021 00:25:29 +0000 Subject: [PATCH 8/8] Black Automatically Formatted Code --- sept_qt/file_input_widget.py | 51 +++++++++++++++--------------------- usage.py | 4 ++- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/sept_qt/file_input_widget.py b/sept_qt/file_input_widget.py index 0610b0e..f001c85 100644 --- a/sept_qt/file_input_widget.py +++ b/sept_qt/file_input_widget.py @@ -38,15 +38,15 @@ class FileTemplateInputWidget(TemplateInputWidget): error class has "location" and "length" attributes on it that can be used to display the highlighting. """ + LOAD_TEXT = "Load SEPT template" SAVE_TEXT = "Save SEPT template" - def __init__(self, parser, error_colour=None, timeout=None, disk_path=None, parent=None): + def __init__( + self, parser, error_colour=None, timeout=None, disk_path=None, parent=None + ): super(FileTemplateInputWidget, self).__init__( - parser=parser, - error_colour=error_colour, - timeout=timeout, - parent=parent + parser=parser, error_colour=error_colour, timeout=timeout, parent=parent ) self._load_from_disk_button = None self._save_to_disk_button = None @@ -68,17 +68,16 @@ def load_path(self, path): template_str = self._read_from_path(path) except errors.SeptError as err: import traceback - message = "Error loading template data from {path}\n" \ - "Error was: {error}\n" \ - "{long_error}"\ - .format( - path=path, - error=str(err), - long_error=traceback.format_exc() + + message = ( + "Error loading template data from {path}\n" + "Error was: {error}\n" + "{long_error}".format( + path=path, error=str(err), long_error=traceback.format_exc() ) + ) self._display_error( - message=message, - title="Error loading template data from disk!" + message=message, title="Error loading template data from disk!" ) return else: # No errors @@ -94,11 +93,11 @@ def _build_input_widget(self): self._load_from_disk_button = QtWidgets.QPushButton("...") self._load_from_disk_button.setMaximumWidth(24) self._load_from_disk_button.clicked.connect( - self._handle_load_disk_button_clicked) + self._handle_load_disk_button_clicked + ) self._save_to_disk_button = QtWidgets.QPushButton("Save...") self._save_to_disk_button.setMaximumWidth(56) - self._save_to_disk_button.clicked.connect( - self._handle_save_disk_button_clicked) + self._save_to_disk_button.clicked.connect(self._handle_save_disk_button_clicked) widget.layout().addWidget(line_edit_widget) widget.layout().addWidget(self._load_from_disk_button) @@ -142,20 +141,16 @@ def _handle_save_disk_button_clicked(self): if not self.template: self._display_information( message="Input text box does not contain a valid template. Please fix any errors or type one.", - title="No valid template" + title="No valid template", ) return path, path_is_dir = self._get_folder_path() - new_path, _ = QtWidgets.QFileDialog.getSaveFileName( - self, - self.SAVE_TEXT, - path - ) + new_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, self.SAVE_TEXT, path) with open(new_path, "w") as fh: fh.write(self.template._template_str) self._display_information( message="Template saved successfully to {}".format(new_path), - title="Success!" + title="Success!", ) @QtCore.Slot() @@ -164,15 +159,11 @@ def _handle_load_disk_button_clicked(self): if path_is_dir: new_path = QtWidgets.QFileDialog.getExistingDirectory( - self, - self.LOAD_TEXT, - path + self, self.LOAD_TEXT, path ) else: new_path, _ = QtWidgets.QFileDialog.getOpenFileName( - self, - self.LOAD_TEXT, - path + self, self.LOAD_TEXT, path ) self._disk_path = new_path self.load_path(self._disk_path) diff --git a/usage.py b/usage.py index eb57ec7..983e647 100644 --- a/usage.py +++ b/usage.py @@ -244,7 +244,9 @@ def _build_ui(self): if not os.path.exists(template_path): with open(template_path, "w") as fh: fh.write("{{sequence}}/{{shot}}_v{{version}}.{{extension}}") - self.template_input_widget = FileTemplateInputWidget(self.parser, disk_path=template_path, parent=self) + self.template_input_widget = FileTemplateInputWidget( + self.parser, disk_path=template_path, parent=self + ) self.template_preview_widget = TemplatePreviewWidget(self.version_data) self.template_input_widget.template_changed.connect( self.template_preview_widget.preview_template