-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheditor.py
233 lines (199 loc) · 8.27 KB
/
editor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""yaje"""
import json
import sys
from PyQt5.QtWidgets import (
QAction,
QApplication,
QFileDialog,
QInputDialog,
QMainWindow,
QMessageBox,
QPlainTextEdit,
QSplitter,
QTextBrowser,
QToolBar,
QTreeWidget,
QTreeWidgetItem,
QVBoxLayout,
QWidget
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor
class NeXusEditor(QMainWindow):
"""Main editor window definition."""
def __init__(self):
super().__init__()
self.setWindowTitle("NeXus JSON Editor")
# main window
main_splitter = QSplitter(Qt.Horizontal)
# tree
self.tree = QTreeWidget()
self.tree.setColumnCount(2)
self.tree.setHeaderLabels(["Key", "Value"])
# self.tree.setHeaderLabel("JSON Structure")
self.tree.itemSelectionChanged.connect(self.on_tree_selection)
# Connect tree updates to JSON updates
self.tree.itemChanged.connect(self.update_json_from_tree)
self.tree.itemSelectionChanged.connect(lambda: self.details.setText(self.tree.currentItem().text(0)))
left_layout = QVBoxLayout()
left_layout.addWidget(self.tree)
# toolbar
toolbar = QToolBar("Actions")
left_layout.addWidget(toolbar)
load_action = QAction("Load JSON", self)
load_action.triggered.connect(self.load_json)
toolbar.addAction(load_action)
add_action = QAction("Add Element", self)
add_action.triggered.connect(self.add_element)
toolbar.addAction(add_action)
remove_action = QAction("Remove Element", self)
remove_action.triggered.connect(self.remove_element)
toolbar.addAction(remove_action)
# left panel
left_widget = QWidget()
left_widget.setLayout(left_layout)
main_splitter.addWidget(left_widget)
# upper right panel
right_splitter = QSplitter(Qt.Vertical)
self.json_editor = QPlainTextEdit()
self.json_editor.setPlaceholderText("{}")
right_splitter.addWidget(self.json_editor)
# lower right panel
self.details = QTextBrowser()
right_splitter.addWidget(self.details)
main_splitter.addWidget(right_splitter)
self.setCentralWidget(main_splitter)
# debug
with open("/Users/georgeoneill/ess-dmsc/nexus-json-templates/dream/dream_beam_monitor_simple.json", "r", encoding="utf-8") as file:
self.loaded_json = json.load(file)
self.populate_tree_from_json(self.loaded_json)
def on_tree_selection(self):
"""Panel description updater."""
selected_item = self.tree.currentItem()
if not selected_item:
return
# Get the path to the selected item
path = self.get_item_path(selected_item)
# Find and highlight the corresponding JSON in the editor
self.highlight_json_in_editor(path)
def get_item_path(self, item):
"""
Constructs a path from the root to the given tree item.
"""
path = []
while item is not None:
text = item.text(0) # Get the text of the current item
# Check if the text is an index (list item)
if text.startswith("[") and text.endswith("]"):
path.append(int(text.strip("[]"))) # Convert index to integer
else:
path.append(text) # Append dictionary key
# Move up to the parent
item = item.parent()
path.reverse() # Reverse the path to start from the root
return path
def highlight_json_in_editor(self, path):
"""Highlights text corresponding to the selected tree path."""
try:
# Get the raw JSON text
json_text = self.json_editor.toPlainText()
data = json.loads(json_text) # Parse JSON
target_data = self.navigate_json(data, path) # Get target data
# Convert the target data to a JSON string for precise matching
target_text = json.dumps(target_data, indent=1).strip()
# Find and highlight in the editor
self.json_editor.moveCursor(QTextCursor.Start)
if self.json_editor.find(target_text):
self.json_editor.setFocus()
else:
QMessageBox.warning(self, "Not Found", f"Could not find {path} in the JSON editor.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to highlight JSON: {e}")
def navigate_json(self, data, path):
for key in path:
if isinstance(data, list): # If current level is a list
key = int(key) # Convert key to integer for index access
data = data[key]
return data
# Load JSON file
def load_json(self):
path, _ = QFileDialog.getOpenFileName(self, "Load JSON File", "", "JSON Files (*.json)")
if path:
with open(path, "r") as file:
self.loaded_json = json.load(file)
self.populate_tree_from_json(self.loaded_json)
# Populate tree from JSON
def populate_tree_from_json(self, data, parent_item=None):
if parent_item is None:
self.tree.clear()
parent_item = self.tree.invisibleRootItem()
for key, value in data.items():
item = QTreeWidgetItem(parent_item)
item.setText(0, key) # Column 0: Key
if isinstance(value, dict):
# Nested dictionary: Recursive call
self.populate_tree_from_json(value, item)
elif isinstance(value, list):
# Handle lists by creating child items for each element
item.setText(1, "[List]") # Indicate this is a list in Column 1
for i, sub_value in enumerate(value):
list_item = QTreeWidgetItem(item)
list_item.setText(0, f"[{i}]") # Use index as key
if isinstance(sub_value, dict):
self.populate_tree_from_json(sub_value, list_item)
else:
list_item.setText(1, str(sub_value)) # Show list values in Column 1
else:
# Leaf node: Show the value in Column 1
item.setText(1, str(value))
# Add element
def add_element(self):
selected_item = self.tree.currentItem()
if not selected_item:
QMessageBox.warning(self, "No Selection", "Please select an item to add a sub-element.")
return
new_key, ok = QInputDialog.getText(self, "Add Element", "Enter the key for the new element:")
if ok and new_key:
new_item = QTreeWidgetItem(selected_item)
new_item.setText(0, new_key)
selected_item.addChild(new_item)
# Remove element
def remove_element(self):
selected_item = self.tree.currentItem()
if not selected_item:
QMessageBox.warning(self, "No Selection", "Please select an item to remove.")
return
reply = QMessageBox.question(
self, "Confirm Removal",
f"Are you sure you want to remove '{selected_item.text(0)}'?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
parent_item = selected_item.parent()
if parent_item:
parent_item.removeChild(selected_item)
else:
root = self.tree.invisibleRootItem()
root.removeChild(selected_item)
def update_json_from_tree(self):
def recurse_tree(item):
if item.childCount() == 0:
return item.text(1) # Use second column as the value
data = {}
for i in range(item.childCount()):
child = item.child(i)
data[child.text(0)] = recurse_tree(child)
return data
root = self.tree.invisibleRootItem()
self.current_json = {}
for i in range(root.childCount()):
child = root.child(i)
self.current_json[child.text(0)] = recurse_tree(child)
# Update the JSON editor
self.json_editor.setPlainText(json.dumps(self.current_json, indent=1))
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = NeXusEditor()
editor.resize(1920, 1080)
editor.show() # launch window
sys.exit(app.exec_())