class FormWidget(QFrame, object): accepted = Signal(object) stateChanged = Signal() validated = Signal() def __init__(self, *args, **kwargs): super(FormWidget, self).__init__(*args, **kwargs) self._schema = dict() self._widgets = list() self._validator = None main_layout = layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) self.setLayout(main_layout) self._fields_frame = QFrame(self) self._fields_frame.setObjectName('fieldsFrame') options_layout = layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) self._fields_frame.setLayout(options_layout) self._title_widget = buttons.BaseButton(parent=self) self._title_widget.setCheckable(True) self._title_widget.setObjectName('titleWidget') self._title_widget.toggled.connect(self._on_title_clicked) self._title_widget.hide() main_layout.addWidget(self._title_widget) main_layout.addWidget(self._fields_frame) # ================================================================================================================= # OVERRIDES # ================================================================================================================= def closeEvent(self, event): self.save_persistent_values() super(FormWidget, self).closeEvent(event) # ================================================================================================================= # BASE # ================================================================================================================= def title_widget(self): """ Returns the title widget :return: QWidget """ return self._title_widget def set_title(self, title): """ Sets the title text :param title: str """ self.title_widget().setText(title) def is_expanded(self): """ Returns whether the item is expanded or not :return: bool """ return self._title_widget.isChecked() def set_expanded(self, flag): """ Expands the options if True, otherwise collapses the options :param flag: bool """ with qt_contexts.block_signals(self._title_widget): self._title_widget.setChecked(flag) self._fields_frame.setVisible(flag) def set_title_visible(self, flag): """ Sets whether the title widget is visible or not :param flag: bool """ self.title_widget().setVisible(flag) def widget(self, name): """ Returns the widget for the given widget name :param name: str :return: FieldWidget """ for widget in self._widgets: if widget.data().get('name') == name: return widget def value(self, name): """ Returns the value for the given widget name :param name: str :return: object """ widget = self.widget(name) if not widget: return None return widget.value() def set_value(self, name, value): """ Sets the value for the given field name :param name: str :param value: variant """ widget = self.widget(name) widget.set_value(value) def values(self): """ Returns all the field values indexed by the field name :return: dict """ values = dict() for widget in self._widgets: name = widget.data().get('name') if name: values[name] = widget.value() return values def set_values(self, values): """ Sets the field values for the current form :param values: dict """ state = list() for name in values: state.append({'name': name, 'value': values[name]}) self._set_state(state) def default_values(self): """ Returns all teh default field values indexed by the field name :return: dict """ values = dict() for widget in self._widgets: name = widget.data().get('name') if name: values[name] = widget.default() return values def set_data(self, name, data): """ Sets the data for the given field name :param name: str :param data: dict """ widget = self.widget(name) if not widget: return widget.set_data(data) def fields(self): """ Returns fields data for the form :return: list(dict) """ options = list() for widget in self._widgets: options.append(widget.data()) return options def field_widgets(self): """ Returns all field widgets :return: list(FieleWidget) """ return self._widgets def state(self): """ Returns the current state :return: dict """ fields = list() for widget in self._widgets: fields.append(widget.state()) state = { 'fields': fields, 'expanded': self.is_expanded() } return state def set_state(self, state): """ Sets the current state :param state: dict """ expanded = state.get('expanded') if expanded is not None: self.set_expanded(expanded) fields = state.get('fields') if fields is not None: self._set_state(fields) self.validate() def schema(self): """ Returns form's schema :return: dict """ return self._schema def set_schema(self, schema, layout=None, errors_visible=False): """ Sets the schema for the widget :param schema: list(dict) :param layout: str :param errors_visible: str """ self._schema = self._sort_schema(schema) if not self._schema: return for field in schema: cls = formfields.FIELD_WIDGET_REGISTRY.get(field.get('type', 'label')) if not cls: LOGGER.warning('Cannot find widget for {}'.format(field)) continue if layout and not field.get('layout'): field['layout'] = layout enabled = field.get('enabled', True) read_only = field.get('readOnly', False) error_visible = field.get('errorVisible') field['errorVisible'] = error_visible if error_visible is not None else errors_visible widget = cls(data=field, parent=self._fields_frame, form_widget=self) data = widget.default_data() data.update(field) widget.set_data(data) value = field.get('value') default = field.get('default') if value is None and default is not None: widget.set_value(default) if not enabled or read_only: widget.setEnabled(False) self._widgets.append(widget) callback = partial(self._on_field_changed, widget) widget.valueChanged.connect(callback) self._fields_frame.layout().addWidget(widget) self.load_persistent_values() def validator(self): """ Returns the validator for the form :return: fn """ return self._validator def set_validator(self, validator): """ Sets the validator for the options :param validator: fn """ self._validator = validator def reset(self): """ Reset all option widget back to the ir default values """ for widget in self._widgets: widget.reset() self.validate() def validate(self, widget=None): """ Validates the current options using the validator """ if not self._validator: return values = dict() for name, value in self.values().items(): data = self.widget(name).data() if data.get('validate', True): values[name] = value if widget: values['fieldChanged'] = widget.name() fields = self._validator(**values) if fields is not None: self._set_state(fields) self.validated.emit() def errors(self): """ Returns all form errors :return: list(str) """ errors = list() for widget in self._widgets: error = widget.data().get('error') if error: errors.append(error) return errors def has_errors(self): """ Returns whether the form contains any error :return: bool """ return bool(self.errors()) def save_persistent_values(self): """ Saves form widget values Triggered when the user changes field values """ data = dict() for widget in self._widgets: name = widget.data().get('name') if name and widget.data().get('persistent'): key = self.objectName() or 'FormWidget' key = widget.data().get('persistentKey', key) data.setdefault(key, dict()) data[key][name] = widget.value() for key in data: settings.set(key, data[key]) def load_persistent_values(self): """ Returns the options from the user settings :return: dict """ values = dict() default_values = self.default_values() for field in self.schema(): name = field.get('name') persistent = field.get('persistent') if persistent: key = self.objectName() or 'FormWidget' key = field.get('persistentKey', key) value = settings.get(key, dict()).get(name) else: value = default_values.get(name) if value is not None: values[name] = value self.set_values(values) # ============================================================================================================ # INTERNAL # ============================================================================================================ def _sort_schema(self, schema): """ Internal function that sorts the schema depending on the group order :param schema: list(dict) :return: list(dict) """ def _key(field): return field['order'] order = 0 if not schema: return for i, field in enumerate(schema): if field.get('type') == 'group': order = field.get('order', order) field['order'] = order return sorted(schema, key=_key) def _set_state(self, fields): """ Internal function that sets fields state :param fields: list(dict) """ for widget in self._widgets: widget.blockSignals(True) try: for widget in self._widgets: widget.set_error('') for field in fields: if field.get('name') == widget.data().get('name'): widget.set_data(field) finally: for widget in self._widgets: widget.blockSignals(False) self.stateChanged.emit() # ============================================================================================================ # CALLBACKS # ============================================================================================================ def _on_title_clicked(self, toggle): """ Internal callback function that is triggered when the user clicks in the title widget """ self.set_expanded(toggle) self.stateChanged.emit() def _on_field_changed(self, widget): """ Internal callback function triggered when the given option widget changes its value :param widget: FieldWidget """ self.validate(widget=widget)
class Divider(QWidget, object): _ALIGN_MAP = {Qt.AlignCenter: 50, Qt.AlignLeft: 20, Qt.AlignRight: 80} def __init__(self, text=None, shadow=True, orientation=Qt.Horizontal, alignment=Qt.AlignLeft, parent=None): """ Basic standard splitter with optional text :param str text: Optional text to include as title in the splitter :param bool shadow: True if you want a shadow above the splitter :param Qt.Orientation orientation: Orientation of the splitter :param Qt.Align alignment: Alignment of the splitter :param QWidget parent: Parent of the splitter """ super(Divider, self).__init__(parent=parent) self._orient = orientation self._text = None main_layout = layouts.HorizontalLayout(spacing=0, margins=(0, 0, 0, 0)) self.setLayout(main_layout) self._label = label.BaseLabel().strong(True) first_line = QFrame() self._second_line = QFrame() main_layout.addWidget(first_line) main_layout.addWidget(self._label) main_layout.addWidget(self._second_line) if orientation == Qt.Horizontal: first_line.setFrameShape(QFrame.HLine) first_line.setFrameShadow(QFrame.Sunken) first_line.setFixedHeight( 2) if shadow else first_line.setFixedHeight(1) self._second_line.setFrameShape(QFrame.HLine) self._second_line.setFrameShadow(QFrame.Sunken) self._second_line.setFixedHeight( 2) if shadow else self._second_line.setFixedHeight(1) else: self._label.setVisible(False) self._second_line.setVisible(False) first_line.setFrameShape(QFrame.VLine) first_line.setFrameShadow(QFrame.Plain) self.setFixedWidth(2) first_line.setFixedWidth( 2) if shadow else first_line.setFixedWidth(1) main_layout.setStretchFactor(first_line, self._ALIGN_MAP.get(alignment, 50)) main_layout.setStretchFactor(self._second_line, 100 - self._ALIGN_MAP.get(alignment, 50)) self.set_text(text) @classmethod def left(cls, text=''): """ Creates an horizontal splitter with text at left :param text: :return: """ return cls(text, alignment=Qt.AlignLeft) @classmethod def right(cls, text=''): """ Creates an horizontal splitter with text at right :param text: :return: """ return cls(text, alignment=Qt.AlignRight) @classmethod def center(cls, text=''): """ Creates an horizontal splitter with text at center :param text: :return: """ return cls(text, alignment=Qt.AlignCenter) @classmethod def vertical(cls): """ Creates a vertical splitter :return: """ return cls(orientation=Qt.Vertical) def get_text(self): """ Returns splitter text :return: str """ return self._label.text() def set_text(self, text): """ Sets splitter text :param str text: """ self._text = text self._label.setText(text) if self._orient == Qt.Horizontal: self._label.setVisible(bool(text)) self._second_line.setVisible(bool(text))
class BaseSaveWidget(base.BaseWidget, object): cancelled = Signal() saved = Signal() ENABLE_THUMBNAIL_CAPTURE = True def __init__(self, item_view, client=None, *args, **kwargs): self._item_view = item_view self._client = client self._form_widget = None self._sequence_widget = None super(BaseSaveWidget, self).__init__(*args, **kwargs) self.setObjectName('LibrarySaveWidget') self._create_sequence_widget() self.update_thumbnail_size() self.set_item_view(item_view) # ============================================================================================================ # OVERRIDES # ============================================================================================================ def get_main_layout(self): return layouts.VerticalLayout(spacing=4, margins=(0, 0, 0, 0)) def ui(self): super(BaseSaveWidget, self).ui() self.setWindowTitle('Save Item') title_frame = QFrame(self) title_frame_layout = layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) title_frame.setLayout(title_frame_layout) title_widget = QFrame(self) title_layout = layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) title_widget.setLayout(title_layout) title_buttons_layout = layouts.HorizontalLayout(spacing=0, margins=(0, 0, 0, 0)) title_layout.addLayout(title_buttons_layout) title_icon = label.BaseLabel(parent=self) title_button = label.BaseLabel(self.item().menu_name(), parent=self) title_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._menu_button = buttons.BaseButton(parent=self) self._menu_button.setIcon(resources.icon('menu_dots')) self._menu_button.setVisible(False) # Hide by default title_buttons_layout.addWidget(title_icon) title_buttons_layout.addSpacing(5) title_buttons_layout.addWidget(title_button) title_buttons_layout.addWidget(self._menu_button) title_frame_layout.addWidget(title_widget) item_icon_name = self.item().icon() or 'tpDcc' item_icon = resources.icon(item_icon_name) if not item_icon: item_icon = resources.icon('tpDcc') title_icon.setPixmap(item_icon.pixmap(QSize(20, 20))) thumbnail_layout = layouts.HorizontalLayout(spacing=0, margins=(0, 0, 0, 0)) self._thumbnail_frame = QFrame(self) thumbnail_frame_layout = layouts.VerticalLayout(spacing=0, margins=(0, 2, 0, 2)) self._thumbnail_frame.setLayout(thumbnail_frame_layout) thumbnail_layout.addWidget(self._thumbnail_frame) self._options_frame = QFrame(self) options_frame_layout = layouts.VerticalLayout(spacing=0, margins=(4, 2, 4, 2)) self._options_frame.setLayout(options_frame_layout) preview_buttons_frame = QFrame(self) self._preview_buttons_layout = layouts.HorizontalLayout(spacing=0, margins=(4, 2, 4, 2)) preview_buttons_frame.setLayout(self._preview_buttons_layout) self._save_button = buttons.BaseButton('Save', parent=self) self._save_button.setIcon(resources.icon('save')) self._cancel_button = buttons.BaseButton('Cancel', parent=self) self._cancel_button.setIcon(resources.icon('cancel')) self._preview_buttons_layout.addStretch() self._preview_buttons_layout.addWidget(self._save_button) self._preview_buttons_layout.addStretch() self._preview_buttons_layout.addWidget(self._cancel_button) self._preview_buttons_layout.addStretch() self.main_layout.addWidget(title_frame) self.main_layout.addLayout(thumbnail_layout) self.main_layout.addWidget(self._options_frame) self.main_layout.addWidget(preview_buttons_frame) def setup_signals(self): self._menu_button.clicked.connect(self._on_show_menu) self._save_button.clicked.connect(self._on_save) self._cancel_button.clicked.connect(self._on_cancel) def resizeEvent(self, event): """ Overrides base QWidget resizeEvent function :param event: QResizeEvent """ self.update_thumbnail_size() def close(self): """ Overrides base QWidget close function to disable script job when its is done """ if self._form_widget: self._form_widget.save_persistent_values() super(BaseSaveWidget, self).close() # ============================================================================================================ # BASE # ============================================================================================================ def folder_path(self): """ Returns the folder path :return: str """ return self.form_widget().value('folder') def set_folder_path(self, path): """ Sets the destination folder path :param path: str """ self.form_widget().set_value('folder', path) def set_thumbnail_path(self, path): """ Sets the path to the thumbnail image or the image sequence directory :param path: str """ file_name, extension = os.path.splitext(path) target = utils.temp_path('thumbnail{}'.format(extension)) utils.copy_path(path, target, force=True) self._sequence_widget.set_path(target) def library_window(self): """ Returns library widget window for the item :return: LibraryWindow """ return self.item_view().library_window() def set_library_window(self, library_window): """ Sets the library widget for the item :param library_window: LibraryWindow """ self.item_view().set_library_window(library_window) def form_widget(self): """ Returns the form widget instance :return: FormWidget """ return self._form_widget def item(self): """ Returns current item :return: """ return self.item_view().item def item_view(self): """ Returns the current item view :return: LibraryItem """ return self._item_view def set_item_view(self, item_view): """ Sets the base item to be created :param item_view: LibraryItem """ self._item_view = item_view if os.path.exists(item_view.image_sequence_path()): self.set_thumbnail_path(item_view.image_sequence_path()) elif not item_view.is_default_thumbnail_path(): self.set_thumbnail_path(item_view.thumbnail_path()) schema = self.item().save_schema() if schema: form_widget = formwidget.FormWidget(self) form_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) form_widget.set_schema(schema) form_widget.set_validator(self.item().save_validator) # item_name = os.path.basename(item.path()) # form_widget.set_values({'name': item_name}) self._options_frame.layout().addWidget(form_widget) form_widget.validate() self._form_widget = form_widget else: self._options_frame.setVisible(False) def update_thumbnail_size(self): """ Updates the thumbnail button to teh size of the widget """ width = self.width() - 10 if width > 250: width = 250 size = QSize(width, width) if self._sequence_widget: self._sequence_widget.setIconSize(size) self._sequence_widget.setMaximumSize(size) self._thumbnail_frame.setMaximumSize(size) def show_thumbnail_capture_dialog(self): """ Asks the user if they would like to capture a thumbnail :return: int """ buttons = QDialogButtonBox.Yes | QDialogButtonBox.Ignore | QDialogButtonBox.Cancel parent = self.item_view().library_window() btn = messagebox.MessageBox.question( parent, 'Create a thumbnail', 'Would you like to capture a thumbnail?', buttons=buttons) if btn == QDialogButtonBox.Yes: self.thumbnail_capture() return btn def show_by_frame_dialog(self): """ Show the by frame dialog """ help_text = """ To help speed up the playblast you can set the "by frame" to another greater than 1. For example if the "by frame" is set to 2 it will playblast every second frame """ result = None options = self.form_widget().values() by_frame = options.get('byFrame', 1) start_frame, end_frame = options.get('frameRange', [None, None]) duration = end_frame - start_frame if start_frame is not None and end_frame is not None else 1 if duration > 100 and by_frame == 1: buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel result = messagebox.MessageBox.question( self.library_window(), title='Tip', text=help_text, buttons=buttons, enable_dont_show_checkbox=True) return result def thumbnail_capture(self, show=False): """ Captures a playblast and saves it to the temporal thumbnail path :param show: bool """ options = self.form_widget().values() start_frame, end_frame = options.get('frameRange', [None, None]) step = options.get('byFrame', 1) if not qtutils.is_control_modifier(): result = self.show_by_frame_dialog() if result == QDialogButtonBox.Cancel: return path = utils.temp_path('sequence', 'thumbnail.jpg') try: snapshot.SnapshotWindow(path=path, on_save=self._on_thumbnail_captured) # thumbnail.ThumbnailCaptureDialog.thumbnail_capture( # path=self._temp_path, # show=show, # start_frame=start_frame, # end_frame=end_frame, # step=step, # clear_cache=False, # captured=self._on_thumbnail_captured # ) except Exception as e: messagebox.MessageBox.critical(self.library_window(), 'Error while capturing thumbnail', str(e)) LOGGER.error(traceback.format_exc()) def save(self, path, thumbnail): """ Saves the item with the given objects to the given disk location path :param path: str :param thumbnail: str """ kwargs = self.form_widget().values() sequence_path = self._sequence_widget.dirname() item_view = self.item_view() item_view.item_view.path = path library_window = self.library_window() valid_save = item_view.safe_save(thumbnail=thumbnail, sequence_path=sequence_path, **kwargs) if valid_save: if library_window: library_window.refresh() library_window.select_folder_path(path) self.saved.emit() self.close() # ============================================================================================================ # INTERNAL # ============================================================================================================ def _create_sequence_widget(self): """ Internal function that creates a sequence widget to replace the static thumbnail widget """ self._sequence_widget = sequence.ImageSequenceWidget(self) self._sequence_widget.setObjectName('thumbnailButton') self._thumbnail_frame.layout().insertWidget(0, self._sequence_widget) self._sequence_widget.clicked.connect(self._on_thumbnail_capture) self._sequence_widget.setToolTip( 'Click to capture a thumbnail from the current model panel.\n' 'CTRL + Click to show the capture window for better framing.') camera_icon = resources.get('icons', self.theme().style(), 'camera.png') expand_icon = resources.get('icons', self.theme().style(), 'full_screen.png') folder_icon = resources.get('icons', self.theme().style(), 'folder.png') self._sequence_widget.addAction(camera_icon, 'Capture new image', 'Capture new image', self._on_thumbnail_capture) self._sequence_widget.addAction(expand_icon, 'Show Capture window', 'Show Capture window', self._on_show_capture_window) self._sequence_widget.addAction(folder_icon, 'Load image from disk', 'Load image from disk', self._on_show_browse_image_dialog) self._sequence_widget.setIcon(resources.icon('tpdcc')) # ============================================================================================================ # CALLBACKS # ============================================================================================================ def _on_show_menu(self): """ Internal callback function that is called when menu button is clicked byu the user :return: QAction """ pass def _on_save(self): if not self.library_window(): return False library = self.library_window().library() if not library: return False try: self.form_widget().validate() if self.form_widget().has_errors(): raise Exception('\n'.join(self.form_widget().errors())) has_frames = self._sequence_widget.has_frames() if not has_frames and self.ENABLE_THUMBNAIL_CAPTURE: button = self.show_thumbnail_capture_dialog() if button == QDialogButtonBox.Cancel: return False name = self.form_widget().value('name') folder = self.form_widget().value('folder') comment = self.form_widget().value('comment') or '' extension = self.item().extension() if extension and not name.endswith(extension): name = '{}{}'.format(name, extension) path = folder + '/' + name thumbnail = self._sequence_widget.first_frame() save_item = library.get(path, only_extension=True) save_function = save_item.functionality().get('save') if not save_function: LOGGER.warning( 'Item "{}" does not supports save operation'.format( save_item)) return False library_path = self.item().library.identifier if not library_path or not os.path.isfile(library_path): LOGGER.warning( 'Impossible to save data "{}" because its library does not exists: "{}"' .format(self.item(), library_path)) return values = self.form_widget().values() try: if self._client: success, message, dependencies = self._client().save_data( library_path=library_path, data_path=path, values=values) if not success: messagebox.MessageBox.critical(self.library_window(), 'Error while saving', str(message)) LOGGER.error(str(message)) return False else: dependencies = save_function(**values) except Exception as exc: messagebox.MessageBox.critical(self.library_window(), 'Error while saving', str(exc)) LOGGER.error(traceback.format_exc()) return False except Exception as exc: messagebox.MessageBox.critical(self.library_window(), 'Error while saving', str(exc)) LOGGER.error(traceback.format_exc()) raise new_item_path = save_item.format_identifier() if not new_item_path or not os.path.isfile(new_item_path): LOGGER.warning( 'Although saving process for item "{}" was completed, ' 'it seems no new data has been generated!'.format(save_item)) self.saved.emit() return False save_item.library.add(new_item_path) # # TODO: Instead of creating a local version, we will use a git system to upload our data to our project repo # # TODO: Should we save new versions of dependencies too? # valid = save_item.create_version(comment=comment) # if not valid: # LOGGER.warning('Impossible to store new version for data "{}"'.format(save_item)) if thumbnail and os.path.isfile(thumbnail): save_item.store_thumbnail(thumbnail) self.library_window().sync() save_item.update_dependencies(dependencies=dependencies) self.saved.emit() return True def _on_cancel(self): self.cancelled.emit() self.close() def _on_thumbnail_capture(self): """ Internal callback function that is called when a thumbnail capture must be done """ self.thumbnail_capture(show=False) def _on_thumbnail_captured(self, captured_path): """ Internal callback function that is called when thumbnail is captured :param captured_path: str """ thumb_path = os.path.dirname(captured_path) self.set_thumbnail_path(thumb_path) def _on_show_capture_window(self): """ Internal callback function that shows the capture window for framing """ self.thumbnail_capture(show=True) def _on_show_browse_image_dialog(self): """ Internal callback function that shows a file dialog for choosing an image from disk """ file_dialog = QFileDialog(self, caption='Open Image', filter='Image Files (*.png *.jpg)') file_dialog.fileSelected.connect(self.set_thumbnail_path) file_dialog.exec_()
class GroupBoxWidget(base.BaseFrame): toggled = Signal(bool) def __init__(self, title, widget, persistent=False, settings=None, *args, **kwargs): self._title = title self._widget = None self._persistent = None self._settings = settings super(GroupBoxWidget, self).__init__(*args, **kwargs) if widget: self.set_widget(widget) # We force the update of the check status to make sure that the wrapped widget visibility is updated self.set_checked(self.is_checked()) self.set_persistent(persistent) # ============================================================================================================ # OVERRIDES # ============================================================================================================ def get_main_layout(self): return layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) def ui(self): super(GroupBoxWidget, self).ui() self._title_widget = buttons.BaseButton(self._title, parent=self) self._title_widget.setCheckable(True) self._on_icon = resources.icon('down_button') self._off_icon = resources.icon('right_button') self._title_widget.setIcon(self._off_icon) self._widget_frame = QFrame(self) self._widget_frame.setObjectName('contentsWidget') widget_frame_layout = layouts.VerticalLayout(spacing=2, margins=(0, 0, 0, 0)) self._widget_frame.setLayout(widget_frame_layout) self.main_layout.addWidget(self._title_widget) self.main_layout.addWidget(self._widget_frame) def setup_signals(self): self._title_widget.toggled.connect(self._on_toggled_title) # ============================================================================================================ # BASE # ============================================================================================================ def is_checked(self): """ Returns whether or not group box is checked :return: bool """ return self._title_widget.isChecked() def set_checked(self, flag): """ Sets the check statue of the group box :param flag: bool """ self._title_widget.setChecked(flag) self._title_widget.setIcon(self._on_icon if flag else self._off_icon) self._widget_frame.setVisible(flag) if self._widget: self._widget.setVisible(flag) def is_persistent(self): """ Returns whether or not widget state is stored in settings :return: bool """ return self._persistent def set_persistent(self, flag): """ Sets whether or not widget state is stored in settings :param flag: bool """ self._persistent = flag self.load_settings() def title(self): """ Returns group box title :return: str """ return self._title_widget.text() def set_widget(self, widget): """ Sets the widget to hide when the user clicks the title :param widget: QWidget """ self._widget = widget self._widget.setParent(self._widget_frame) self._widget_frame.layout().addWidget(self._widget) # ============================================================================================================ # SETTINGS # ============================================================================================================ def load_settings(self): """ Loads widget state from given settings """ if not self._settings or not self._persistent: return if not self.objectName(): raise NameError( 'Impossible to save "{}" widget state because no objectName is defined!' .format(self)) data = {self.objectName(): {'checked': self.is_checked()}} self._settings.save(data) def save_settings(self): """ Saves current widget state into settings """ if not self._settings or not self._persistent: return if not self.objectName(): raise NameError( 'Impossible to load "{}" widget state because no objectName is defined!' .format(self)) data = self._settings.read() data = data.get(self.objectName(), dict()) if data and isinstance(data, dict): checked = data.get('checked', True) self.set_checked(checked) # ============================================================================================================ # CALLBACKS # ============================================================================================================ def _on_toggled_title(self, flag): """ Internal callback function that is called each time title group widget is toggled :param flag: bool """ self.save_settings() self.set_checked(flag) self.toggled.emit(flag)
class BaseSaveWidget(base.BaseWidget, object): def __init__(self, item, settings, temp_path=None, parent=None): # self._item = None self._settings = settings self._temp_path = temp_path self._options_widget = None # super(BaseSaveWidget, self).__init__(parent=parent) # # self.setObjectName('LibrarySaveWidget') # self.set_item(item) def ui(self): super(BaseSaveWidget, self).ui() title_layout = layouts.HorizontalLayout() title_layout.setContentsMargins(2, 2, 0, 0) title_layout.setSpacing(2) self._icon_lbl = QLabel() self._icon_lbl.setMaximumSize(QSize(14, 14)) self._icon_lbl.setMinimumSize(QSize(14, 14)) self._icon_lbl.setScaledContents(True) self._title_lbl = QLabel() title_layout.addWidget(self._icon_lbl) title_layout.addWidget(self._title_lbl) self._folder_widget = directory.SelectFolder('Folder', use_app_browser=True) buttons_layout = layouts.HorizontalLayout() buttons_layout.setContentsMargins(4, 4, 4, 4) buttons_layout.setSpacing(4) buttons_frame = QFrame() buttons_frame.setFrameShape(QFrame.NoFrame) buttons_frame.setFrameShadow(QFrame.Plain) buttons_frame.setLayout(buttons_layout) buttons_layout.addStretch() self.save_btn = buttons.BaseButton('Save') self.cancel_btn = buttons.BaseButton('Cancel') buttons_layout.addWidget(self.save_btn, parent=self) buttons_layout.addWidget(self.cancel_btn, parent=self) buttons_layout.addStretch() self._options_layout = layouts.VerticalLayout() self._options_layout.setContentsMargins(0, 0, 0, 0) self._options_layout.setSpacing(2) self._options_frame = QFrame() self._options_frame.setFrameShape(QFrame.NoFrame) self._options_frame.setFrameShadow(QFrame.Plain) self._options_frame.setLineWidth(0) self._options_frame.setLayout(self._options_layout) self._extra_layout = layouts.VerticalLayout() self._extra_layout.setContentsMargins(0, 0, 0, 0) self._extra_layout.setSpacing(2) self.main_layout.addLayout(title_layout) self.main_layout.addWidget(self._folder_widget) self._extra_layout.addWidget(self._options_frame) self.main_layout.addWidget(dividers.Divider()) self.main_layout.addLayout(self._extra_layout) self.main_layout.addWidget(dividers.Divider()) self.main_layout.addWidget(buttons_frame) def setup_signals(self): self.save_btn.clicked.connect(self._on_save) self.cancel_btn.clicked.connect(self._on_cancel) def settings(self): """ Returns settings object :return: JSONSettings """ return self._settings def set_settings(self, settings): """ Sets save widget settings :param settings: JSONSettings """ self._settings = settings # def item(self): # """ # Returns the library item to be created # :return: LibraryItem # """ # # return self._item def set_item(self, item): """ Sets the base item to be created :param item: LibraryItem """ self._item = item self._title_lbl.setText(item.MenuName) self._icon_lbl.setPixmap(QPixmap(item.type_icon_path())) schema = item.save_schema() if schema: options_widget = formwidget.FormWidget(self) options_widget.set_schema(schema) options_widget.set_validator(item.save_validator) self._options_frame.layout().addWidget(options_widget) self._options_widget = options_widget self.load_settings() options_widget.stateChanged.connect(self._on_options_changed) options_widget.validate() else: self._options_frame.setVisible(False) def library_window(self): """ Returns library widget window for the item :return: LibraryWindow """ return self._item.library_window() def set_library_window(self, library_window): """ Sets the library widget for the item :param library_window: LibraryWindow """ self._item.set_library_window(library_window) def name(self): """ Returns the name of the field :return: str """ return self._title_lbl.text().strip() def description(self): """ Returns the string from the comment field :return: str """ return self._comment.toPlainText().strip() def folder_frame(self): """ Returns the frame that contains the folder edit, label and button :return: QFrame """ return self._folder_frame def folder_path(self): """ Returns the folder path :return: str """ return self._folder_widget.folder_line.text() def set_folder_path(self, path): """ Sets the destination folder path :param path: str """ self._folder_widget.folder_line.setText(path) def default_values(self): """ Returns all the default values for the save fields :return: dict """ values = dict() for option in self.item().save_schema(): values[option.get('name')] = option.get('default') return values def save_settings(self): """ Saves the current state of the widget to disk """ state = self._options_widget.options_state() self.settings().set(self.item().__class__.__name__, {'SaveOptions': state}) def load_settings(self): """ Returns the settings object for saving the state of the widget """ option_settings = self.settings().get(self.item().__class__.__name__, {}) options = option_settings.get('SaveOptions', dict()) values = self.default_values() if options: for option in self.item().save_schema(): name = option.get('name') persistent = option.get('persistent') if not persistent and name in options: options[name] = values[name] self._options_widget.set_state_from_options(options) def save(self, path, icon_path, objects=None): """ Saves the item with the given objects to the given disk location path :param path: list(str) :param icon_path: str :param objects: str """ item = self.item() options = self._options_widget.values() item.save(path=path, objects=objects, icon_path=icon_path, **options) self.close() def _save(self): if not self.library_window(): return try: path = self.folder_path() options = self._options_widget.values() name = options.get('name') objects = dcc.selected_nodes(full_path=True) or list() if not path: raise Exception( 'No folder selected. Please select a destination folder') if not name: raise Exception( 'No name specified. Please set a name before saving') if not os.path.exists(self.icon_path()): btn = self.show_thumbnail_capture_dialog() if btn == QDialogButtonBox.Cancel: return path += '/{}'.format(name) icon_path = self.icon_path() self.save(path=path, icon_path=icon_path, objects=objects) except Exception as e: messagebox.MessageBox.critical(self.library_window(), 'Error while saving', str(e)) LOGGER.error(traceback.format_exc()) raise self.library_window().stack.slide_in_index(0) def _on_options_changed(self): """ Internal callback function that is called when an option value changes """ self.save_settings() def _on_save(self): if not self.library_window(): return self._save() self.library_window().stack.slide_in_index(0) def _on_cancel(self): self.close() self.library_window().stack.slide_in_index(0)