-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add gui for editing custom variables added in hab==0.35.0
- Loading branch information
1 parent
731f31e
commit 6ec8c99
Showing
12 changed files
with
564 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from Qt import QtWidgets | ||
|
||
from .. import utils | ||
from ..widgets.custom_variable_editor import CustomVariableEditor | ||
|
||
|
||
class EditCustomVariablesAction(QtWidgets.QAction): | ||
"""A QAction that allows the user to edit custom variables. | ||
Shows a dialog showing any config/distro json files that have editing enabled | ||
by setting the top level dict variable `variable_editor` to `True`. Users can | ||
then add or remove variables, and edit their keys and values. | ||
Args: | ||
resolver (hab.Resolver): The resolver used for settings. | ||
hab_widget (QWidget): The URI widget menu operations are performed on. | ||
verbosity (int, optional): The current verbosity setting. | ||
parent (Qt.QtWidgets.QWidget, optional): Define a parent for this widget. | ||
""" | ||
|
||
def __init__(self, resolver, hab_widget, verbosity=0, parent=None): | ||
super().__init__( | ||
utils.Paths.icon("pencil-box-outline.svg"), | ||
"Edit Custom Variables", | ||
parent, | ||
) | ||
self.hab_widget = hab_widget | ||
self.resolver = resolver | ||
self.verbosity = verbosity | ||
self.setObjectName("edit_custom_variables") | ||
|
||
self.triggered.connect(self.edit_custom_variables) | ||
|
||
def edit_custom_variables(self): | ||
dlg = CustomVariableEditor.create_dialog( | ||
self.resolver, verbosity=self.verbosity, parent=self.parent() | ||
) | ||
dlg.exec_() | ||
|
||
# Ensure the hab_gui respects any changes the user may have made | ||
self.hab_widget.refresh_cache() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .custom_variable_editor import CustomVariableEditor # noqa: F401 |
166 changes: 166 additions & 0 deletions
166
hab_gui/widgets/custom_variable_editor/custom_variable_editor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import logging | ||
|
||
from Qt import QtCore, QtWidgets | ||
|
||
from ... import utils | ||
from .file_tree_widget_item import FileTreeWidgetItem | ||
from .variable_tree_widget_item import VariableTreeWidgetItem | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class CustomVariableEditor(QtWidgets.QWidget): | ||
"""A widget that can view and edit custom variables in hab configs/distros. | ||
This widget will only show config/distro files that have `variable_editor` | ||
set to `True` in the top level dict. | ||
Args: | ||
resolver (hab.Resolver): The resolver to change verbosity settings on. | ||
verbosity (int): Change the verbosity setting to this value. If None is passed, | ||
all results are be shown without any filtering. | ||
parent (Qt.QtWidgets.QWidget, optional): Define a parent for this widget. | ||
""" | ||
|
||
def __init__(self, resolver, verbosity=0, parent=None): | ||
super().__init__(parent) | ||
self._refresh_on_show = True | ||
self.resolver = resolver | ||
self.verbosity = verbosity | ||
utils.load_ui(__file__, self) | ||
self.setWindowIcon(utils.Paths.icon("habihat.svg")) | ||
|
||
self.uiAddVariableBTN.setIcon(utils.Paths.icon("plus-thick.svg")) | ||
self.uiEditCurrentItemBTN.setIcon(utils.Paths.icon("pencil-box-outline.svg")) | ||
self.uiResetBTN.setIcon(utils.Paths.icon("refresh.svg")) | ||
self.uiRemoveVariableBTN.setIcon(utils.Paths.icon("minus-thick.svg")) | ||
self.uiSaveBTN.setIcon(utils.Paths.icon("content-save.svg")) | ||
|
||
# Configure editing of widget items. _is_refreshing is used to only | ||
# prevent updating the model while refreshing, not other signals. | ||
self._is_refreshing = False | ||
self.uiVariableTREE.model().dataChanged.connect(self.editing_finished) | ||
|
||
def add_variable(self): | ||
"""Add a new variable to the selected FileTreeWidgetItem""" | ||
item = self.uiVariableTREE.currentItem() | ||
if "Undefined" in item.parser.variables: | ||
QtWidgets.QMessageBox.information( | ||
self, | ||
"Variable already defined", | ||
"You already have a variable named Undefined. Change the name " | ||
"of that variable before adding a new one.", | ||
) | ||
else: | ||
item.parser.variables["Undefined"] = "Undefined" | ||
VariableTreeWidgetItem(item, "Undefined") | ||
# Mark the item as dirty | ||
item.dirty = True | ||
|
||
@property | ||
def dirty(self): | ||
"""Generator that yields any FileTreeWidgetItem's that are modified.""" | ||
for index in range(self.uiVariableTREE.topLevelItemCount()): | ||
child = self.uiVariableTREE.topLevelItem(index) | ||
if child.dirty: | ||
yield child | ||
|
||
def edit_cell(self): | ||
"""Edit the currently selected cell""" | ||
index = self.uiVariableTREE.currentIndex() | ||
self.uiVariableTREE.edit(index) | ||
|
||
def editing_finished(self, top_left, bottom_right, roles): | ||
if self._is_refreshing: | ||
return | ||
|
||
if QtCore.Qt.EditRole in roles: | ||
item = self.uiVariableTREE.itemFromIndex(top_left) | ||
column = top_left.column() | ||
if column == 0: | ||
item.variable_name = item.text(column) | ||
elif column == 1: | ||
item.value = item.text(column) | ||
|
||
def current_changed(self, current=None, previous=None): | ||
"""Enable buttons based on the current selection.""" | ||
item = self.uiVariableTREE.currentItem() | ||
is_file = isinstance(item, FileTreeWidgetItem) | ||
self.uiAddVariableBTN.setEnabled(is_file) | ||
self.uiEditCurrentItemBTN.setEnabled(not is_file) | ||
self.uiRemoveVariableBTN.setEnabled(not is_file) | ||
|
||
@utils.cursor_override() | ||
def refresh(self): | ||
self._is_refreshing = True | ||
try: | ||
self.uiVariableTREE.clear() | ||
|
||
for forest in (self.resolver.configs, self.resolver.distros): | ||
for row in self.resolver.dump_forest(forest, attr=None): | ||
parser = row.node | ||
if parser.filename: | ||
if parser.load(parser.filename).get("variable_editor", False): | ||
FileTreeWidgetItem(self.uiVariableTREE, parser) | ||
|
||
self.uiVariableTREE.expandAll() | ||
self.uiVariableTREE.resizeColumnToContents(0) | ||
|
||
self.current_changed() | ||
finally: | ||
self._is_refreshing = False | ||
|
||
@property | ||
def refresh_on_show(self): | ||
"""Should this automatically refresh when the widget is shown.""" | ||
return self._refresh_on_show | ||
|
||
@refresh_on_show.setter | ||
def refresh_on_show(self, state): | ||
self._refresh_on_show = state | ||
|
||
def remove_variable(self): | ||
"""Remove the currently selected variable""" | ||
item = self.uiVariableTREE.currentItem() | ||
item.remove_variable() | ||
parent = item.parent() | ||
idx = parent.indexOfChild(item) | ||
parent.takeChild(idx) | ||
parent.dirty = True | ||
|
||
def reset(self): | ||
"""Revert any un-saved changes.""" | ||
self.resolver.clear_caches() | ||
self.refresh() | ||
|
||
def save(self): | ||
"""Save all changes to disk""" | ||
for parser in self.dirty: | ||
parser.save() | ||
|
||
# Re-display the saved data | ||
self.reset() | ||
|
||
def showEvent(self, event): # noqa: N802 | ||
super().showEvent(event) | ||
if self.refresh_on_show: | ||
self.refresh() | ||
|
||
@classmethod | ||
def create_dialog(cls, resolver, verbosity=0, title="Edit Variables", parent=None): | ||
"""Create a simple standalone QDialog containing this widget. | ||
Args: | ||
resolver (hab.Resolver): The resolver to change verbosity settings on. | ||
verbosity (int): Change the verbosity setting to this value. If None is passed, | ||
all results are be shown without any filtering. | ||
title (str, optional): The window title of the created dialog. | ||
parent (Qt.QtWidgets.QWidget, optional): Define a parent for this widget. | ||
""" | ||
dlg = QtWidgets.QDialog(parent=parent) | ||
dlg.setWindowTitle(title) | ||
dlg.setWindowIcon(utils.Paths.icon("pencil-box-outline.svg")) | ||
layout = QtWidgets.QVBoxLayout(dlg) | ||
dlg.uiVariableWGT = cls(resolver, verbosity=verbosity, parent=dlg) | ||
layout.addWidget(dlg.uiVariableWGT) | ||
return dlg |
86 changes: 86 additions & 0 deletions
86
hab_gui/widgets/custom_variable_editor/file_tree_widget_item.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import json | ||
|
||
import hab.utils | ||
from Qt import QtCore, QtWidgets | ||
|
||
from .variable_tree_widget_item import VariableTreeWidgetItem | ||
|
||
|
||
class FileTreeWidgetItem(QtWidgets.QTreeWidgetItem): | ||
"""A QTreeWidgetItem used to show a given config/distro and its custom variables.""" | ||
|
||
def __init__(self, parent, parser): | ||
super().__init__(parent) | ||
self.parser = parser | ||
# Add a tracking variable to tell if the parser is dirty | ||
if not hasattr(self.parser, "dirty"): | ||
self.parser.dirty = False | ||
|
||
# Add a child item that shows the filename. It should not be editable. | ||
self.filename_item = QtWidgets.QTreeWidgetItem(self) | ||
self.filename_item.setFlags(QtCore.Qt.NoItemFlags) | ||
|
||
self.refresh() | ||
|
||
@property | ||
def dirty(self): | ||
return self.parser.dirty | ||
|
||
@dirty.setter | ||
def dirty(self, state): | ||
changed = state != self.parser.dirty | ||
self.parser.dirty = state | ||
if changed: | ||
self.setText(0, self.name) | ||
|
||
@property | ||
def name(self): | ||
name = self.parser.name | ||
if self.dirty: | ||
return f"{name}*" | ||
return name | ||
|
||
def refresh(self): | ||
self.setText(0, self.name) | ||
self.filename_item.setText(0, "Filename") | ||
self.filename_item.setText(1, str(self.parser.filename)) | ||
|
||
for index, variable_name in enumerate(self.parser.variables): | ||
# Get the existing variable item if possible. Index 0 is the | ||
# filename item. | ||
item = self.child(index + 1) | ||
if item: | ||
item.variable_name = variable_name | ||
item.refresh() | ||
else: | ||
# Otherwise add a new item | ||
VariableTreeWidgetItem(self, variable_name) | ||
|
||
# If any variables were removed, remove their tree widget items | ||
variable_count = len(self.parser.variables) + 1 | ||
for _ in range(variable_count, self.childCount() + 1): | ||
self.removeChild(self.child(variable_count)) | ||
|
||
def save(self): | ||
"""Save the variable changes to disk. | ||
NOTE: This saves the data as regular json data not json5. Any comments, | ||
etc will be cleared by calling this method. | ||
Returns: | ||
bool: Returns if this was dirty and updated data was saved to disk. | ||
""" | ||
if not self.dirty: | ||
return False | ||
|
||
# Reload data from disk | ||
raw_data = hab.utils.load_json_file(self.parser.filename) | ||
|
||
# Update the variables section with the changes. | ||
raw_data["variables"] = self.parser.variables | ||
|
||
# Save changes over top of the existing file. | ||
with self.parser.filename.open("w") as fle: | ||
json.dump(raw_data, fle, indent=4, cls=hab.utils.HabJsonEncoder) | ||
|
||
return True |
Oops, something went wrong.