|
|
|
@ -198,9 +198,10 @@ class TimeSeriesPanel(ViewPanel): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataTreeNode: |
|
|
|
|
def __init__(self, name: str, full_path: str = ""): |
|
|
|
|
def __init__(self, name: str, full_path: str = "", parent = None): |
|
|
|
|
self.name = name |
|
|
|
|
self.full_path = full_path |
|
|
|
|
self.parent = parent |
|
|
|
|
self.children: dict[str, DataTreeNode] = {} |
|
|
|
|
self.is_leaf = False |
|
|
|
|
self.child_count = 0 |
|
|
|
@ -225,6 +226,7 @@ class DataTreeView: |
|
|
|
|
self.data_manager.add_observer(self._on_data_loaded) |
|
|
|
|
self.queued_search = None |
|
|
|
|
self.new_data = False |
|
|
|
|
self._ui_lock = threading.RLock() |
|
|
|
|
|
|
|
|
|
def create_ui(self, parent_tag: str): |
|
|
|
|
with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1): |
|
|
|
@ -264,7 +266,7 @@ class DataTreeView: |
|
|
|
|
if i < len(parts) - 1: |
|
|
|
|
parent_nodes_to_recheck.add(current_node) # for incremental changes from new data |
|
|
|
|
if part not in current_node.children: |
|
|
|
|
current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix) |
|
|
|
|
current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix, parent=current_node) |
|
|
|
|
current_node = current_node.children[part] |
|
|
|
|
|
|
|
|
|
if not current_node.is_leaf: |
|
|
|
@ -278,6 +280,7 @@ class DataTreeView: |
|
|
|
|
return target_tree |
|
|
|
|
|
|
|
|
|
def update_frame(self, font): |
|
|
|
|
with self._ui_lock: |
|
|
|
|
if self.avg_char_width is None and dpg.is_dearpygui_running(): |
|
|
|
|
self.avg_char_width = self.calculate_avg_char_width(font) |
|
|
|
|
|
|
|
|
@ -313,10 +316,6 @@ class DataTreeView: |
|
|
|
|
self.queued_search = dpg.get_value("search_input") |
|
|
|
|
|
|
|
|
|
def _clear_ui(self): |
|
|
|
|
for handler_tag in self._item_handlers: |
|
|
|
|
dpg.delete_item(handler_tag) |
|
|
|
|
self._item_handlers.clear() |
|
|
|
|
|
|
|
|
|
if dpg.does_item_exist("data_tree_container"): |
|
|
|
|
dpg.delete_item("data_tree_container", children_only=True) |
|
|
|
|
|
|
|
|
@ -341,6 +340,9 @@ class DataTreeView: |
|
|
|
|
label = f"{node.name} ({node.child_count} fields)" |
|
|
|
|
search_term = self.current_search.strip().lower() |
|
|
|
|
should_open = bool(search_term) and len(search_term) > 1 and any(search_term in path for path in self._get_descendant_paths(node)) |
|
|
|
|
if should_open and node.parent and node.parent.child_count > 100 and node.child_count > 2: |
|
|
|
|
label += " (+)" |
|
|
|
|
should_open = False |
|
|
|
|
|
|
|
|
|
with dpg.tree_node(label=label, parent=parent_tag, tag=tag, default_open=should_open, open_on_arrow=True, open_on_double_click=True, before=before): |
|
|
|
|
with dpg.item_handler_registry(tag=handler_tag): |
|
|
|
@ -374,6 +376,7 @@ class DataTreeView: |
|
|
|
|
node.ui_tag = f"value_{node.full_path}" |
|
|
|
|
|
|
|
|
|
def _on_item_visible(self, sender, app_data, user_data): |
|
|
|
|
with self._ui_lock: |
|
|
|
|
path = user_data |
|
|
|
|
group_tag = f"group_{path}" |
|
|
|
|
value_tag = f"value_{path}" |
|
|
|
@ -392,10 +395,8 @@ class DataTreeView: |
|
|
|
|
dpg.set_value(value_tag, "N/A") |
|
|
|
|
|
|
|
|
|
def _request_children_build(self, node: DataTreeNode, handler_tag=None): |
|
|
|
|
with self._ui_lock: |
|
|
|
|
if not node.children_ui_created and (node.name == "root" or (node.ui_tag is not None and dpg.get_value(node.ui_tag))): # check root or node expanded |
|
|
|
|
if handler_tag and dpg.does_item_exist(handler_tag): |
|
|
|
|
dpg.delete_item(handler_tag) |
|
|
|
|
self._item_handlers.discard(handler_tag) |
|
|
|
|
parent_tag = "data_tree_container" if node.name == "root" else node.ui_tag |
|
|
|
|
sorted_children = sorted(node.children.values(), key=self._natural_sort_key) |
|
|
|
|
|
|
|
|
|