Merge pull request #772 from pypeclub/feature/unified_settings_category_widget

Unified settings category widget
This commit is contained in:
Milan Kolar 2020-12-02 21:17:32 +01:00 committed by GitHub
commit ab7585a9ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 236 additions and 360 deletions

View file

@ -4,7 +4,7 @@
"children": [{
"type": "schema",
"name": "schema_general"
},{
}, {
"type": "schema",
"name": "schema_modules"
}, {

View file

@ -33,25 +33,44 @@ from avalon import io
from avalon.vendor import qtawesome
class SystemWidget(QtWidgets.QWidget):
is_overidable = False
has_studio_override = _has_studio_override = False
is_overriden = _is_overriden = False
as_widget = _as_widget = False
any_parent_as_widget = _any_parent_as_widget = False
is_group = _is_group = False
any_parent_is_group = _any_parent_is_group = False
class SettingsCategoryWidget(QtWidgets.QWidget):
schema_category = None
initial_schema_name = None
def __init__(self, user_role, parent=None):
super(SystemWidget, self).__init__(parent)
super(SettingsCategoryWidget, self).__init__(parent)
self.user_role = user_role
self.initialize_attributes()
self.create_ui()
self.reset()
def initialize_attributes(self):
self._hide_studio_overrides = False
self._ignore_value_changes = False
self.keys = []
self.input_fields = []
self.environ_fields = []
self.schema = None
self.main_schema_key = None
# Required attributes for items
self.is_overidable = False
self._has_studio_override = False
self._is_overriden = False
self._as_widget = False
self._is_group = False
self._any_parent_as_widget = False
self._any_parent_is_group = False
self.has_studio_override = self._has_studio_override
self.is_overriden = self._is_overriden
self.as_widget = self._as_widget
self.is_group = self._as_widget
self.any_parent_as_widget = self._any_parent_as_widget
self.any_parent_is_group = self._any_parent_is_group
def create_ui(self):
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setObjectName("GroupWidget")
content_widget = QtWidgets.QWidget(scroll_widget)
@ -59,63 +78,93 @@ class SystemWidget(QtWidgets.QWidget):
content_layout.setContentsMargins(3, 3, 3, 3)
content_layout.setSpacing(0)
content_layout.setAlignment(QtCore.Qt.AlignTop)
content_widget.setLayout(content_layout)
scroll_widget.setWidgetResizable(True)
scroll_widget.setWidget(content_widget)
self.scroll_widget = scroll_widget
self.content_layout = content_layout
self.content_widget = content_widget
footer_widget = QtWidgets.QWidget()
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
if self.user_role == "developer":
save_as_default_btn = QtWidgets.QPushButton("Save as Default")
save_as_default_btn.clicked.connect(self._save_as_defaults)
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton()
refresh_button.setIcon(refresh_icon)
refresh_button.clicked.connect(self._on_refresh)
hide_studio_overrides = QtWidgets.QCheckBox()
hide_studio_overrides.setChecked(self._hide_studio_overrides)
hide_studio_overrides.stateChanged.connect(
self._on_hide_studio_overrides
)
hide_studio_overrides_widget = QtWidgets.QWidget()
hide_studio_overrides_layout = QtWidgets.QHBoxLayout(
hide_studio_overrides_widget
)
_label_widget = QtWidgets.QLabel(
"Hide studio overrides", hide_studio_overrides_widget
)
hide_studio_overrides_layout.addWidget(_label_widget)
hide_studio_overrides_layout.addWidget(hide_studio_overrides)
footer_layout.addWidget(save_as_default_btn, 0)
footer_layout.addWidget(refresh_button, 0)
footer_layout.addWidget(hide_studio_overrides_widget, 0)
self._add_developer_ui(footer_layout)
save_btn = QtWidgets.QPushButton("Save")
spacer_widget = QtWidgets.QWidget()
footer_layout.addWidget(spacer_widget, 1)
footer_layout.addWidget(save_btn, 0)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.setLayout(layout)
configurations_widget = QtWidgets.QWidget()
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
configurations_layout.setContentsMargins(0, 0, 0, 0)
configurations_layout.setSpacing(0)
layout.addWidget(scroll_widget, 1)
layout.addWidget(footer_widget, 0)
configurations_layout.addWidget(scroll_widget, 1)
configurations_layout.addWidget(footer_widget, 0)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addWidget(configurations_widget, 1)
save_btn.clicked.connect(self._save)
self.reset()
self.scroll_widget = scroll_widget
self.content_layout = content_layout
self.content_widget = content_widget
self.configurations_widget = configurations_widget
self.main_layout = main_layout
self.ui_tweaks()
def ui_tweaks(self):
return
def _add_developer_ui(self, footer_layout):
save_as_default_btn = QtWidgets.QPushButton("Save as Default")
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton()
refresh_button.setIcon(refresh_icon)
hide_studio_overrides = QtWidgets.QCheckBox()
hide_studio_overrides.setChecked(self._hide_studio_overrides)
hide_studio_overrides_widget = QtWidgets.QWidget()
hide_studio_overrides_layout = QtWidgets.QHBoxLayout(
hide_studio_overrides_widget
)
_label_widget = QtWidgets.QLabel(
"Hide studio overrides", hide_studio_overrides_widget
)
hide_studio_overrides_layout.addWidget(_label_widget)
hide_studio_overrides_layout.addWidget(hide_studio_overrides)
footer_layout.addWidget(save_as_default_btn, 0)
footer_layout.addWidget(refresh_button, 0)
footer_layout.addWidget(hide_studio_overrides_widget, 0)
save_as_default_btn.clicked.connect(self._save_as_defaults)
refresh_button.clicked.connect(self._on_refresh)
hide_studio_overrides.stateChanged.connect(
self._on_hide_studio_overrides
)
def save(self):
"""Save procedure."""
raise NotImplementedError("Method `save` is not implemented.")
def defaults_dir(self):
"""Path to defaults folder."""
raise NotImplementedError("Method `defaults_dir` is not implemented.")
def update_values(self):
"""Procedure of update values of items on context change or reset."""
raise NotImplementedError("Method `update_values` is not implemented.")
def validate_defaults_to_save(self, value):
raise NotImplementedError(
"Method `validate_defaults_to_save` not implemented."
)
def any_parent_overriden(self):
return False
@ -134,21 +183,22 @@ class SystemWidget(QtWidgets.QWidget):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
def add_environ_field(self, input_field):
self.environ_fields.append(input_field)
def reset(self):
reset_default_settings()
self.keys.clear()
self.input_fields.clear()
self.environ_fields.clear()
while self.content_layout.count() != 0:
widget = self.content_layout.itemAt(0).widget()
self.content_layout.removeWidget(widget)
widget.deleteLater()
self.schema = lib.gui_schema("system_schema", "schema_main")
self.keys = self.schema.get("keys", [])
self.schema = lib.gui_schema(
self.schema_category, self.initial_schema_name
)
self.main_schema_key = self.schema["key"]
self.add_children_gui(self.schema)
self._update_values()
self.hierarchical_style_update()
@ -180,6 +230,95 @@ class SystemWidget(QtWidgets.QWidget):
first_invalid_item.setFocus(True)
return False
def _save(self):
if not self.items_are_valid():
return
self.save()
self._update_values()
def _on_refresh(self):
self.reset()
def _on_hide_studio_overrides(self, state):
self._hide_studio_overrides = (state == QtCore.Qt.Checked)
self._update_values()
self.hierarchical_style_update()
def _save_as_defaults(self):
if not self.items_are_valid():
return
all_values = {}
for item in self.input_fields:
all_values.update(item.config_value())
for key in reversed(self.keys):
all_values = {key: all_values}
# Skip first key and convert data to store
all_values = lib.convert_gui_data_with_metadata(
all_values[self.main_schema_key]
)
if not self.validate_defaults_to_save(all_values):
return
defaults_dir = self.defaults_dir()
keys_to_file = lib.file_keys_from_schema(self.schema)
for key_sequence in keys_to_file:
# Skip first key
key_sequence = key_sequence[1:]
subpath = "/".join(key_sequence) + ".json"
new_values = all_values
for key in key_sequence:
new_values = new_values[key]
output_path = os.path.join(defaults_dir, subpath)
dirpath = os.path.dirname(output_path)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
print("Saving data to: ", subpath)
with open(output_path, "w") as file_stream:
json.dump(new_values, file_stream, indent=4)
reset_default_settings()
self._update_values()
self.hierarchical_style_update()
def _update_values(self):
self.ignore_value_changes = True
self.update_values()
self.ignore_value_changes = False
def add_children_gui(self, child_configuration):
klass = lib.TypeToKlass.types.get(child_configuration["type"])
item = klass(child_configuration, self)
item.create_ui()
self.input_fields.append(item)
self.content_layout.addWidget(item, 0)
# Add spacer to stretch children guis
self.content_layout.addWidget(
QtWidgets.QWidget(self.content_widget), 1
)
class SystemWidget(SettingsCategoryWidget):
schema_category = "system_schema"
initial_schema_name = "schema_main"
def initialize_attributes(self):
self.environ_fields = []
super(SystemWidget, self).initialize_attributes()
def add_environ_field(self, input_field):
self.environ_fields.append(input_field)
def duplicated_env_group_validation(self, values=None, overrides=None):
try:
if overrides is not None:
@ -208,83 +347,35 @@ class SystemWidget(QtWidgets.QWidget):
return False
return True
def _save(self):
if not self.items_are_valid():
return
def defaults_dir(self):
return os.path.join(DEFAULTS_DIR, SYSTEM_SETTINGS_KEY)
def validate_defaults_to_save(self, values):
return self.duplicated_env_group_validation(values)
def reset(self):
self.environ_fields.clear()
super(SystemWidget, self).reset()
def save(self):
_data = {}
for input_field in self.input_fields:
value, _is_group = input_field.studio_overrides()
if value is not lib.NOT_SET:
_data.update(value)
values = lib.convert_gui_data_to_overrides(_data.get("system", {}))
values = lib.convert_gui_data_to_overrides(
_data.get(self.main_schema_key, {})
)
if not self.duplicated_env_group_validation(overrides=values):
return
save_studio_settings(values)
self._update_values()
def _on_refresh(self):
self.reset()
def _on_hide_studio_overrides(self, state):
self._hide_studio_overrides = (state == QtCore.Qt.Checked)
self._update_values()
self.hierarchical_style_update()
def _save_as_defaults(self):
if not self.items_are_valid():
return
all_values = {}
for item in self.input_fields:
all_values.update(item.config_value())
for key in reversed(self.keys):
_all_values = {key: all_values}
all_values = _all_values
# Skip first key
all_values = lib.convert_gui_data_with_metadata(all_values["system"])
if not self.duplicated_env_group_validation(all_values):
return
prject_defaults_dir = os.path.join(
DEFAULTS_DIR, SYSTEM_SETTINGS_KEY
)
keys_to_file = lib.file_keys_from_schema(self.schema)
for key_sequence in keys_to_file:
# Skip first key
key_sequence = key_sequence[1:]
subpath = "/".join(key_sequence) + ".json"
new_values = all_values
for key in key_sequence:
new_values = new_values[key]
output_path = os.path.join(prject_defaults_dir, subpath)
dirpath = os.path.dirname(output_path)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
print("Saving data to: ", subpath)
with open(output_path, "w") as file_stream:
json.dump(new_values, file_stream, indent=4)
reset_default_settings()
self._update_values()
self.hierarchical_style_update()
def _update_values(self):
self.ignore_value_changes = True
def update_values(self):
default_values = lib.convert_data_to_gui_data({
"system": default_settings()[SYSTEM_SETTINGS_KEY]
self.main_schema_key: default_settings()[SYSTEM_SETTINGS_KEY]
})
for input_field in self.input_fields:
input_field.update_default_values(default_values)
@ -293,26 +384,12 @@ class SystemWidget(QtWidgets.QWidget):
system_values = lib.NOT_SET
else:
system_values = lib.convert_overrides_to_gui_data(
{"system": studio_system_settings()}
{self.main_schema_key: studio_system_settings()}
)
for input_field in self.input_fields:
input_field.update_studio_values(system_values)
self.ignore_value_changes = False
def add_children_gui(self, child_configuration):
item_type = child_configuration["type"]
klass = lib.TypeToKlass.types.get(item_type)
item = klass(child_configuration, self)
item.create_ui()
self.input_fields.append(item)
self.content_layout.addWidget(item, 0)
# Add spacer to stretch children guis
spacer = QtWidgets.QWidget(self.content_widget)
self.content_layout.addWidget(spacer, 1)
class ProjectListView(QtWidgets.QListView):
left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
@ -440,145 +517,30 @@ class ProjectListWidget(QtWidgets.QWidget):
)
class ProjectWidget(QtWidgets.QWidget):
has_studio_override = _has_studio_override = False
is_overriden = _is_overriden = False
as_widget = _as_widget = False
any_parent_as_widget = _any_parent_as_widget = False
is_group = _is_group = False
any_parent_is_group = _any_parent_is_group = False
class ProjectWidget(SettingsCategoryWidget):
schema_category = "projects_schema"
initial_schema_name = "schema_main"
def __init__(self, user_role, parent=None):
super(ProjectWidget, self).__init__(parent)
self.user_role = user_role
self._hide_studio_overrides = False
self.is_overidable = False
self._ignore_value_changes = False
def initialize_attributes(self):
self.project_name = None
self.input_fields = []
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setObjectName("GroupWidget")
content_widget = QtWidgets.QWidget(scroll_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(3, 3, 3, 3)
content_layout.setSpacing(0)
content_layout.setAlignment(QtCore.Qt.AlignTop)
content_widget.setLayout(content_layout)
scroll_widget.setWidgetResizable(True)
scroll_widget.setWidget(content_widget)
super(ProjectWidget, self).initialize_attributes()
def ui_tweaks(self):
project_list_widget = ProjectListWidget(self)
content_layout.addWidget(project_list_widget)
footer_widget = QtWidgets.QWidget()
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
self.main_layout.insertWidget(0, project_list_widget, 0)
if self.user_role == "developer":
save_as_default_btn = QtWidgets.QPushButton("Save as Default")
save_as_default_btn.clicked.connect(self._save_as_defaults)
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton()
refresh_button.setIcon(refresh_icon)
refresh_button.clicked.connect(self._on_refresh)
hide_studio_overrides = QtWidgets.QCheckBox()
hide_studio_overrides.setChecked(self._hide_studio_overrides)
hide_studio_overrides.stateChanged.connect(
self._on_hide_studio_overrides
)
hide_studio_overrides_widget = QtWidgets.QWidget()
hide_studio_overrides_layout = QtWidgets.QHBoxLayout(
hide_studio_overrides_widget
)
_label_widget = QtWidgets.QLabel(
"Hide studio overrides", hide_studio_overrides_widget
)
hide_studio_overrides_layout.addWidget(_label_widget)
hide_studio_overrides_layout.addWidget(hide_studio_overrides)
footer_layout.addWidget(save_as_default_btn, 0)
footer_layout.addWidget(refresh_button, 0)
footer_layout.addWidget(hide_studio_overrides_widget, 0)
save_btn = QtWidgets.QPushButton("Save")
spacer_widget = QtWidgets.QWidget()
footer_layout.addWidget(spacer_widget, 1)
footer_layout.addWidget(save_btn, 0)
configurations_widget = QtWidgets.QWidget()
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
configurations_layout.setContentsMargins(0, 0, 0, 0)
configurations_layout.setSpacing(0)
configurations_layout.addWidget(scroll_widget, 1)
configurations_layout.addWidget(footer_widget, 0)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.setLayout(layout)
layout.addWidget(project_list_widget, 0)
layout.addWidget(configurations_widget, 1)
save_btn.clicked.connect(self._save_overrides)
project_list_widget.project_changed.connect(self._on_project_change)
self.project_list_widget = project_list_widget
self.scroll_widget = scroll_widget
self.content_layout = content_layout
self.content_widget = content_widget
self.reset()
def defaults_dir(self):
return DEFAULTS_DIR
def any_parent_overriden(self):
return False
@property
def ignore_value_changes(self):
return self._ignore_value_changes
@ignore_value_changes.setter
def ignore_value_changes(self, value):
self._ignore_value_changes = value
if value is False:
self.hierarchical_style_update()
def hierarchical_style_update(self):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
def reset(self):
self.input_fields.clear()
while self.content_layout.count() != 0:
widget = self.content_layout.itemAt(0).widget()
self.content_layout.removeWidget(widget)
widget.deleteLater()
self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema")
self.keys = self.schema.get("keys", [])
self.add_children_gui(self.schema)
self._update_values()
self.hierarchical_style_update()
def add_children_gui(self, child_configuration):
item_type = child_configuration["type"]
klass = lib.TypeToKlass.types.get(item_type)
item = klass(child_configuration, self)
item.create_ui()
self.input_fields.append(item)
self.content_layout.addWidget(item, 0)
# Add spacer to stretch children guis
spacer = QtWidgets.QWidget(self.content_widget)
self.content_layout.addWidget(spacer, 1)
def validate_defaults_to_save(self, _):
# Projects does not have any specific validations
return True
def _on_project_change(self):
project_name = self.project_list_widget.project_name()
@ -591,7 +553,7 @@ class ProjectWidget(QtWidgets.QWidget):
_project_anatomy = project_anatomy_overrides(project_name)
self.is_overidable = True
overrides = {"project": {
overrides = {self.main_schema_key: {
PROJECT_SETTINGS_KEY: lib.convert_overrides_to_gui_data(
_project_overrides
),
@ -605,103 +567,19 @@ class ProjectWidget(QtWidgets.QWidget):
item.apply_overrides(overrides)
self.ignore_value_changes = False
def _save_as_defaults(self):
output = {}
for item in self.input_fields:
output.update(item.config_value())
for key in reversed(self.keys):
_output = {key: output}
output = _output
all_values = {}
for item in self.input_fields:
all_values.update(item.config_value())
all_values = lib.convert_gui_data_with_metadata(all_values)
for key in reversed(self.keys):
_all_values = {key: all_values}
all_values = _all_values
# Skip first key
all_values = all_values["project"]
keys_to_file = lib.file_keys_from_schema(self.schema)
for key_sequence in keys_to_file:
# Skip first key
key_sequence = key_sequence[1:]
subpath = "/".join(key_sequence) + ".json"
new_values = all_values
for key in key_sequence:
new_values = new_values[key]
output_path = os.path.join(DEFAULTS_DIR, subpath)
dirpath = os.path.dirname(output_path)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
print("Saving data to: ", subpath)
with open(output_path, "w") as file_stream:
json.dump(new_values, file_stream, indent=4)
reset_default_settings()
self._update_values()
self.hierarchical_style_update()
def items_are_valid(self):
has_invalid = False
for item in self.input_fields:
if item.child_invalid:
has_invalid = True
if not has_invalid:
return True
invalid_items = []
for item in self.input_fields:
invalid_items.extend(item.get_invalid())
msg_box = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Warning,
"Invalid input",
"There is invalid value in one of inputs."
" Please lead red color and fix them."
)
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg_box.exec_()
first_invalid_item = invalid_items[0]
self.scroll_widget.ensureWidgetVisible(first_invalid_item)
if first_invalid_item.isVisible():
first_invalid_item.setFocus(True)
return False
def _on_refresh(self):
self.reset()
def _on_hide_studio_overrides(self, state):
self._hide_studio_overrides = (state == QtCore.Qt.Checked)
self._update_values()
self.hierarchical_style_update()
def _save_overrides(self):
if not self.items_are_valid():
return
def save(self):
data = {}
studio_overrides = bool(self.project_name is None)
for item in self.input_fields:
if studio_overrides:
value, is_group = item.studio_overrides()
value, _is_group = item.studio_overrides()
else:
value, is_group = item.overrides()
value, _is_group = item.overrides()
if value is not lib.NOT_SET:
data.update(value)
output_data = lib.convert_gui_data_to_overrides(
data.get("project") or {}
data.get(self.main_schema_key) or {}
)
# Saving overrides data
@ -719,11 +597,9 @@ class ProjectWidget(QtWidgets.QWidget):
# Update saved values
self._update_values()
def _update_values(self):
self.ignore_value_changes = True
def update_values(self):
default_values = lib.convert_data_to_gui_data(
{"project": default_settings()}
{self.main_schema_key: default_settings()}
)
for input_field in self.input_fields:
input_field.update_default_values(default_values)
@ -731,12 +607,12 @@ class ProjectWidget(QtWidgets.QWidget):
if self._hide_studio_overrides:
studio_values = lib.NOT_SET
else:
studio_values = lib.convert_overrides_to_gui_data({"project": {
PROJECT_SETTINGS_KEY: studio_project_settings(),
PROJECT_ANATOMY_KEY: studio_project_anatomy()
}})
studio_values = lib.convert_overrides_to_gui_data({
self.main_schema_key: {
PROJECT_SETTINGS_KEY: studio_project_settings(),
PROJECT_ANATOMY_KEY: studio_project_anatomy()
}
})
for input_field in self.input_fields:
input_field.update_studio_values(studio_values)
self.ignore_value_changes = False