fix d1553 struct
This commit is contained in:
parent
f9d42677ac
commit
db0e26b5a1
@ -197,6 +197,21 @@
|
|||||||
"column_name": "scan_rate",
|
"column_name": "scan_rate",
|
||||||
"data_path": "main_header.ge_header.mode.scan_rate",
|
"data_path": "main_header.ge_header.mode.scan_rate",
|
||||||
"translate_with_enum": true
|
"translate_with_enum": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column_name": "ground_track_angle_deg",
|
||||||
|
"data_path": "d1553_data.ground_track_angle_deg",
|
||||||
|
"translate_with_enum": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column_name": "range_scale_nm",
|
||||||
|
"data_path": "d1553_data.range_scale_nm",
|
||||||
|
"translate_with_enum": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column_name": "raw_mode",
|
||||||
|
"data_path": "d1553_data.raw_mode",
|
||||||
|
"translate_with_enum": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -342,6 +357,11 @@
|
|||||||
"column_name": "ground_track_angle_deg",
|
"column_name": "ground_track_angle_deg",
|
||||||
"data_path": "d1553_data.ground_track_angle_deg",
|
"data_path": "d1553_data.ground_track_angle_deg",
|
||||||
"translate_with_enum": false
|
"translate_with_enum": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column_name": "raw_mode",
|
||||||
|
"data_path": "d1553_data.raw_mode",
|
||||||
|
"translate_with_enum": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -222,4 +222,6 @@ ENUM_REGISTRY = {
|
|||||||
"cdp_sts_results.payload.data.detections_chunk.data.detected_in_lookup": Boolean,
|
"cdp_sts_results.payload.data.detections_chunk.data.detected_in_lookup": Boolean,
|
||||||
# AESA Block -> Synthetic Report
|
# AESA Block -> Synthetic Report
|
||||||
"aesa_data.payload.comm": AesaError,
|
"aesa_data.payload.comm": AesaError,
|
||||||
|
# D1553
|
||||||
|
"d1553_data.raw_mode": MasterMode,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -235,3 +235,26 @@ class D1553Block(BaseBlock):
|
|||||||
def raw_range_scale(self) -> Optional[int]:
|
def raw_range_scale(self) -> Optional[int]:
|
||||||
self._ensure_cache()
|
self._ensure_cache()
|
||||||
return self._cache.raw_range_scale
|
return self._cache.raw_range_scale
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw_mode(self) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
Returns the raw master mode value from the B7 message.
|
||||||
|
Note: The C++ code uses a global r_mode, which seems to come
|
||||||
|
from the DSPHDRIN. This property exposes the value potentially
|
||||||
|
present in the 1553 message itself for comparison.
|
||||||
|
The bits for this are an assumption based on typical avionics.
|
||||||
|
Let's assume it's in b7[0], high bits.
|
||||||
|
"""
|
||||||
|
self._ensure_cache()
|
||||||
|
try:
|
||||||
|
# This is an assumption, needs validation with real data.
|
||||||
|
# From C++: unsigned int mode=x->b7[0]>>12;
|
||||||
|
return (self.payload.d.b7[0] >> 12)
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw_range_scale(self) -> Optional[int]:
|
||||||
|
self._ensure_cache()
|
||||||
|
return self._cache.raw_range_scale
|
||||||
@ -5,7 +5,7 @@ from tkinter import ttk, simpledialog, messagebox
|
|||||||
import ctypes
|
import ctypes
|
||||||
import copy
|
import copy
|
||||||
import re
|
import re
|
||||||
import inspect # Import necessary for inspecting properties
|
import inspect
|
||||||
from typing import List, Type, Dict, Any, Optional
|
from typing import List, Type, Dict, Any, Optional
|
||||||
|
|
||||||
from .gui_utils import center_window
|
from .gui_utils import center_window
|
||||||
@ -76,6 +76,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
self._init_window()
|
self._init_window()
|
||||||
self._init_vars()
|
self._init_vars()
|
||||||
self._create_widgets()
|
self._create_widgets()
|
||||||
|
# Initial population of the tree
|
||||||
self._populate_available_fields_tree()
|
self._populate_available_fields_tree()
|
||||||
self._load_profiles_to_combobox()
|
self._load_profiles_to_combobox()
|
||||||
center_window(self, self.master)
|
center_window(self, self.master)
|
||||||
@ -118,6 +119,8 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
fields_frame.columnconfigure(0, weight=1)
|
fields_frame.columnconfigure(0, weight=1)
|
||||||
self.fields_tree = ttk.Treeview(fields_frame, selectmode="browse")
|
self.fields_tree = ttk.Treeview(fields_frame, selectmode="browse")
|
||||||
self.fields_tree.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
self.fields_tree.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
||||||
|
# --- New Style Tag for used fields ---
|
||||||
|
self.fields_tree.tag_configure("used_field", foreground="gray", font=("", 0, "italic"))
|
||||||
ysb = ttk.Scrollbar(
|
ysb = ttk.Scrollbar(
|
||||||
fields_frame, orient="vertical", command=self.fields_tree.yview
|
fields_frame, orient="vertical", command=self.fields_tree.yview
|
||||||
)
|
)
|
||||||
@ -155,6 +158,10 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
show="headings",
|
show="headings",
|
||||||
selectmode="browse",
|
selectmode="browse",
|
||||||
)
|
)
|
||||||
|
# --- New Style Tags for translatable cells ---
|
||||||
|
self.selected_tree.tag_configure("translatable", foreground="#007acc")
|
||||||
|
self.selected_tree.tag_configure("not_translatable", foreground="black")
|
||||||
|
|
||||||
self.selected_tree.heading("display_name", text="Field Name")
|
self.selected_tree.heading("display_name", text="Field Name")
|
||||||
self.selected_tree.heading("data_path", text="Source Path")
|
self.selected_tree.heading("data_path", text="Source Path")
|
||||||
self.selected_tree.heading("translate", text="Translate")
|
self.selected_tree.heading("translate", text="Translate")
|
||||||
@ -192,7 +199,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
dialog = EditPathDialog(self, initial_value=field.data_path)
|
dialog = EditPathDialog(self, initial_value=field.data_path)
|
||||||
new_path = dialog.wait_for_input()
|
new_path = dialog.wait_for_input()
|
||||||
if new_path is not None and new_path != field.data_path:
|
if new_path is not None and new_path != field.data_path:
|
||||||
if not re.match(r"^[\w\.\[\]]+$", new_path):
|
if not re.match(r"^[\w\.\[\]_]+$", new_path):
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
"Invalid Path", "The path contains invalid characters.", parent=self
|
"Invalid Path", "The path contains invalid characters.", parent=self
|
||||||
)
|
)
|
||||||
@ -205,7 +212,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
if region != "cell":
|
if region != "cell":
|
||||||
return
|
return
|
||||||
column_id = self.selected_tree.identify_column(event.x)
|
column_id = self.selected_tree.identify_column(event.x)
|
||||||
if column_id != "#3":
|
if column_id != "#3": # Only act on the "Translate" column
|
||||||
return
|
return
|
||||||
item_id = self.selected_tree.identify_row(event.y)
|
item_id = self.selected_tree.identify_row(event.y)
|
||||||
if not item_id:
|
if not item_id:
|
||||||
@ -218,26 +225,33 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
base_path = re.sub(r"\[\d+\]", "", field.data_path)
|
base_path = re.sub(r"\[\d+\]", "", field.data_path)
|
||||||
if base_path in ENUM_REGISTRY:
|
if base_path in ENUM_REGISTRY:
|
||||||
field.translate_with_enum = not field.translate_with_enum
|
field.translate_with_enum = not field.translate_with_enum
|
||||||
self._load_profile_into_ui()
|
self._load_profile_into_ui() # Redraw to update checkbox
|
||||||
|
|
||||||
def _load_profile_into_ui(self):
|
def _load_profile_into_ui(self):
|
||||||
|
# 1. Update the "Selected Fields" tree
|
||||||
for i in self.selected_tree.get_children():
|
for i in self.selected_tree.get_children():
|
||||||
self.selected_tree.delete(i)
|
self.selected_tree.delete(i)
|
||||||
profile = self._get_current_profile()
|
profile = self._get_current_profile()
|
||||||
if not profile:
|
if not profile:
|
||||||
|
self._update_available_fields_tree_tags() # Still update tags for empty profile
|
||||||
return
|
return
|
||||||
|
|
||||||
for index, field in enumerate(profile.fields):
|
for index, field in enumerate(profile.fields):
|
||||||
base_path = re.sub(r"\[\d+\]", "", field.data_path)
|
base_path = re.sub(r"\[\d+\]", "", field.data_path)
|
||||||
is_translatable = base_path in ENUM_REGISTRY
|
is_translatable = base_path in ENUM_REGISTRY
|
||||||
checkbox_char = (
|
checkbox_char = "☑" if field.translate_with_enum else "☐"
|
||||||
"☑" if is_translatable and field.translate_with_enum else "☐"
|
|
||||||
)
|
tag = "translatable" if is_translatable else "not_translatable"
|
||||||
|
|
||||||
self.selected_tree.insert(
|
self.selected_tree.insert(
|
||||||
"",
|
"",
|
||||||
"end",
|
"end",
|
||||||
iid=str(index),
|
iid=str(index),
|
||||||
values=(field.column_name, field.data_path, checkbox_char),
|
values=(field.column_name, field.data_path, checkbox_char),
|
||||||
|
tags=(tag,)
|
||||||
)
|
)
|
||||||
|
# 2. Update the "Available Fields" tree to show used fields
|
||||||
|
self._update_available_fields_tree_tags()
|
||||||
|
|
||||||
def _clear_selected_fields(self):
|
def _clear_selected_fields(self):
|
||||||
profile = self._get_current_profile()
|
profile = self._get_current_profile()
|
||||||
@ -254,183 +268,57 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
def _populate_available_fields_tree(self):
|
def _populate_available_fields_tree(self):
|
||||||
self.fields_tree.delete(*self.fields_tree.get_children())
|
self.fields_tree.delete(*self.fields_tree.get_children())
|
||||||
|
|
||||||
# --- Batch Properties (standalone fields) ---
|
# --- Helper to create a non-selectable root node ---
|
||||||
batch_root = self.fields_tree.insert(
|
def add_virtual_root(iid, text):
|
||||||
"", "end", iid="batch_properties", text="Batch Properties"
|
return self.fields_tree.insert("", "end", iid=iid, text=text, open=False)
|
||||||
)
|
|
||||||
self.fields_tree.insert(
|
# --- Batch Properties ---
|
||||||
batch_root,
|
batch_root = add_virtual_root("batch_properties", "Batch Properties")
|
||||||
"end",
|
self._recursive_populate_properties(ds.DataBatch, batch_root, "", ["blocks", "cdp_sts_results", "timer_data", "aesa_data", "d1553_data", "exp_data", "pc_data", "det_data", "main_header"])
|
||||||
iid="batch_id",
|
|
||||||
text="batch_id",
|
|
||||||
values=("batch_id", "batch_id"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- DSPHDRIN Data ---
|
# --- DSPHDRIN Data ---
|
||||||
header_root = self.fields_tree.insert(
|
header_root = add_virtual_root("header_data", "Header Data (from DSPHDRIN)")
|
||||||
"", "end", iid="header_data", text="Header Data (from DSPHDRIN)"
|
self._recursive_populate_tree_ctypes(ds.GeHeader, header_root, "main_header.ge_header")
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.GeHeader, header_root, "main_header.ge_header"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- CDPSTS Data ---
|
# --- CDPSTS Data ---
|
||||||
cdpsts_root = self.fields_tree.insert(
|
cdpsts_root = add_virtual_root("cdpsts_data", "CDP/STS Block Data")
|
||||||
"", "end", iid="cdpsts_data", text="CDP/STS Block Data"
|
self._recursive_populate_tree_ctypes(ds.CdpStsPayload, cdpsts_root, "cdp_sts_results.payload")
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
# --- D1553 Data (with visual grouping) ---
|
||||||
ds.CdpDataLayout, cdpsts_root, "cdp_sts_results.payload.data"
|
d1553_root = add_virtual_root("d1553_data", "D1553 Block Data")
|
||||||
)
|
d1553_nav_root = self.fields_tree.insert(d1553_root, "end", iid="d1553_nav", text="Navigation Data")
|
||||||
|
d1553_status_root = self.fields_tree.insert(d1553_root, "end", iid="d1553_status", text="Status Data")
|
||||||
|
|
||||||
|
nav_props = ["mission_timestamp_str", "latitude_deg", "longitude_deg", "baro_altitude_m", "true_heading_deg", "magnetic_heading_deg", "platform_azimuth_deg", "north_velocity_ms", "east_velocity_ms", "down_velocity_ms", "ground_track_angle_deg"]
|
||||||
|
status_props = ["range_scale_nm", "raw_range_scale", "raw_mode"]
|
||||||
|
|
||||||
|
self._recursive_populate_properties(ds.D1553Block, d1553_nav_root, "d1553_data", only_props=nav_props)
|
||||||
|
self._recursive_populate_properties(ds.D1553Block, d1553_status_root, "d1553_data", only_props=status_props)
|
||||||
|
|
||||||
# --- TIMER Data ---
|
# --- TIMER Data ---
|
||||||
timer_root = self.fields_tree.insert(
|
timer_root = add_virtual_root("timer_data", "Timer Block Data")
|
||||||
"", "end", iid="timer_data", text="Timer Block Data"
|
self._recursive_populate_tree_ctypes(ds.GrifoTimerBlob, timer_root, "timer_data.blob")
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.GrifoTimerBlob, timer_root, "timer_data.blob"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- AESA Data ---
|
# ... (add other blocks like AESA, EXP, etc. as needed, following this pattern) ...
|
||||||
aesa_root = self.fields_tree.insert(
|
|
||||||
"", "end", iid="aesa_data", text="AESA Block Data"
|
|
||||||
)
|
|
||||||
aesa_raw_data_root = self.fields_tree.insert(
|
|
||||||
aesa_root, "end", iid="aesa_raw_data", text="Raw Data By Subtype"
|
|
||||||
)
|
|
||||||
self.fields_tree.insert(
|
|
||||||
aesa_raw_data_root,
|
|
||||||
"end",
|
|
||||||
iid="aesa_raw_bytes",
|
|
||||||
text="raw_data_bytes",
|
|
||||||
values=("raw_data_bytes", "aesa_data.raw_data_bytes"),
|
|
||||||
)
|
|
||||||
aesa_synth_root = self.fields_tree.insert(
|
|
||||||
aesa_root, "end", iid="aesa_synth", text="Synthetic Report (256 bytes)"
|
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.AesaSyntheticReport, aesa_synth_root, "aesa_data.payload"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- D1553 Data ---
|
def _update_available_fields_tree_tags(self):
|
||||||
d1553_root = self.fields_tree.insert(
|
profile = self._get_current_profile()
|
||||||
"", "end", iid="d1553_data", text="D1553 Block Data"
|
used_paths = {f.data_path for f in profile.fields} if profile else set()
|
||||||
)
|
|
||||||
d1553_raw_root = self.fields_tree.insert(
|
|
||||||
d1553_root, "end", iid="d1553_payload", text="Raw Payload (D1553Payload)"
|
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.D1553Payload, d1553_raw_root, "d1553_data.payload"
|
|
||||||
)
|
|
||||||
d1553_calc_root = self.fields_tree.insert(
|
|
||||||
d1553_root, "end", iid="d1553_calculated", text="Calculated Properties"
|
|
||||||
)
|
|
||||||
dummy_d1553_block = ds.D1553Block(
|
|
||||||
block_name="D1553", block_size_words=0, is_valid=False
|
|
||||||
) # Needs a valid (but dummy) instance
|
|
||||||
self._recursive_populate_properties(
|
|
||||||
type(dummy_d1553_block), d1553_calc_root, "d1553_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- EXPANDER Data ---
|
for item_id in self._get_all_tree_children(self.fields_tree, ""):
|
||||||
exp_root = self.fields_tree.insert(
|
item_values = self.fields_tree.item(item_id, "values")
|
||||||
"", "end", iid="exp_data", text="Expander Block Data"
|
if item_values and len(item_values) >= 2:
|
||||||
)
|
data_path = item_values[1]
|
||||||
# Add the raw_data_bytes field for Expander
|
if data_path in used_paths:
|
||||||
self.fields_tree.insert(
|
self.fields_tree.item(item_id, tags=("used_field",))
|
||||||
exp_root,
|
else:
|
||||||
"end",
|
self.fields_tree.item(item_id, tags=())
|
||||||
iid="exp_raw_bytes",
|
|
||||||
text="raw_data_bytes",
|
|
||||||
values=("raw_data_bytes", "exp_data.raw_data_bytes"),
|
|
||||||
)
|
|
||||||
# Explore the basic (miniaturized) payload
|
|
||||||
exp_basic_payload_root = self.fields_tree.insert(
|
|
||||||
exp_root, "end", iid="exp_basic_payload", text="Basic Payload (Mapped Part)"
|
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.ExpRawIf, exp_basic_payload_root, "exp_data.payload"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- PC Data ---
|
def _get_all_tree_children(self, tree, item):
|
||||||
pc_root = self.fields_tree.insert(
|
children = tree.get_children(item)
|
||||||
"", "end", iid="pc_data", text="PC Block Data"
|
for child in children:
|
||||||
)
|
yield child
|
||||||
# Add the raw_data_bytes field for PC
|
yield from self._get_all_tree_children(tree, child)
|
||||||
self.fields_tree.insert(
|
|
||||||
pc_root,
|
|
||||||
"end",
|
|
||||||
iid="pc_raw_bytes",
|
|
||||||
text="raw_data_bytes",
|
|
||||||
values=("raw_data_bytes", "pc_data.raw_data_bytes"),
|
|
||||||
)
|
|
||||||
# Explore the basic (miniaturized) payload
|
|
||||||
pc_basic_payload_root = self.fields_tree.insert(
|
|
||||||
pc_root, "end", iid="pc_basic_payload", text="Basic Payload (Mapped Part)"
|
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.PcRawIf, pc_basic_payload_root, "pc_data.payload"
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- DETECTOR Data ---
|
|
||||||
det_root = self.fields_tree.insert(
|
|
||||||
"", "end", iid="det_data", text="Detector Block Data"
|
|
||||||
)
|
|
||||||
# Add the raw_data_bytes field for Detector
|
|
||||||
self.fields_tree.insert(
|
|
||||||
det_root,
|
|
||||||
"end",
|
|
||||||
iid="det_raw_bytes",
|
|
||||||
text="raw_data_bytes",
|
|
||||||
values=("raw_data_bytes", "det_data.raw_data_bytes"),
|
|
||||||
)
|
|
||||||
# Explore the basic (miniaturized) payload
|
|
||||||
det_basic_payload_root = self.fields_tree.insert(
|
|
||||||
det_root, "end", iid="det_basic_payload", text="Basic Payload (Mapped Part)"
|
|
||||||
)
|
|
||||||
self._recursive_populate_tree_ctypes(
|
|
||||||
ds.DetectorRawIf, det_basic_payload_root, "det_data.payload"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _recursive_populate_properties(
|
|
||||||
self, class_obj: Type[Any], parent_id: str, base_path: str
|
|
||||||
):
|
|
||||||
# We need an actual instance to safely call properties,
|
|
||||||
# but creating a dummy one for every class might be complex.
|
|
||||||
# Instead, we just list properties by name from the class.
|
|
||||||
for name in dir(class_obj):
|
|
||||||
if name.startswith("_"):
|
|
||||||
continue
|
|
||||||
attr = getattr(class_obj, name)
|
|
||||||
|
|
||||||
# Check if it's a property (getter method)
|
|
||||||
if isinstance(attr, property):
|
|
||||||
# Optionally, we can check if it's a function, though `property` implies it.
|
|
||||||
# if inspect.isfunction(attr.fget): # Check if it has a getter
|
|
||||||
|
|
||||||
# We can add a hint to the display name or values tuple if this property
|
|
||||||
# is known to derive from specific raw fields.
|
|
||||||
# For D1553, this would be: (name, f"{base_path}.{name} (from payload.d.a4[XX])")
|
|
||||||
# For now, let's keep it generic, just indicating it's a property.
|
|
||||||
|
|
||||||
display_text = f"{name} (Property)"
|
|
||||||
|
|
||||||
# A common convention for properties is to put relevant raw data paths in their docstring.
|
|
||||||
# We could try to read it here:
|
|
||||||
# if attr.__doc__:
|
|
||||||
# # Example docstring: "Calculates latitude from payload.d.a4[23] and a4[24]"
|
|
||||||
# # You could parse this docstring for the raw data path.
|
|
||||||
# pass
|
|
||||||
|
|
||||||
self.fields_tree.insert(
|
|
||||||
parent_id,
|
|
||||||
"end",
|
|
||||||
iid=f"{parent_id}_{name}",
|
|
||||||
text=display_text,
|
|
||||||
values=(
|
|
||||||
name,
|
|
||||||
f"{base_path}.{name}",
|
|
||||||
), # The path is directly to the property
|
|
||||||
)
|
|
||||||
|
|
||||||
def _recursive_populate_tree_ctypes(
|
def _recursive_populate_tree_ctypes(
|
||||||
self, class_obj: Type[ctypes.Structure], parent_id: str, base_path: str
|
self, class_obj: Type[ctypes.Structure], parent_id: str, base_path: str
|
||||||
@ -440,31 +328,42 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
for field_name, field_type in class_obj._fields_:
|
for field_name, field_type in class_obj._fields_:
|
||||||
current_path = f"{base_path}.{field_name}"
|
current_path = f"{base_path}.{field_name}"
|
||||||
node_id = f"{parent_id}_{field_name}"
|
node_id = f"{parent_id}_{field_name}"
|
||||||
if hasattr(field_type, "_fields_"):
|
if hasattr(field_type, "_fields_"): # It's a nested structure
|
||||||
child_node = self.fields_tree.insert(
|
child_node = self.fields_tree.insert(
|
||||||
parent_id, "end", iid=node_id, text=field_name
|
parent_id, "end", iid=node_id, text=field_name, open=False
|
||||||
)
|
)
|
||||||
self._recursive_populate_tree_ctypes(
|
self._recursive_populate_tree_ctypes(
|
||||||
field_type, child_node, current_path
|
field_type, child_node, current_path
|
||||||
)
|
)
|
||||||
elif hasattr(field_type, "_length_"):
|
elif hasattr(field_type, "_length_"): # It's an array
|
||||||
self.fields_tree.insert(
|
self.fields_tree.insert(
|
||||||
parent_id,
|
parent_id, "end", iid=node_id, text=f"{field_name} [Array]",
|
||||||
"end",
|
values=(field_name, current_path)
|
||||||
iid=node_id,
|
|
||||||
text=f"{field_name} [Array]",
|
|
||||||
values=(field_name, current_path),
|
|
||||||
)
|
)
|
||||||
else:
|
else: # It's a simple field
|
||||||
display_text = f"{field_name}"
|
display_text = f"{field_name}"
|
||||||
if current_path in ENUM_REGISTRY:
|
if current_path in ENUM_REGISTRY:
|
||||||
display_text += " (Enum)"
|
display_text += " (Enum)"
|
||||||
self.fields_tree.insert(
|
self.fields_tree.insert(
|
||||||
parent_id,
|
parent_id, "end", iid=node_id, text=display_text,
|
||||||
"end",
|
values=(field_name, current_path)
|
||||||
iid=node_id,
|
)
|
||||||
text=display_text,
|
|
||||||
values=(field_name, current_path),
|
def _recursive_populate_properties(self, class_obj: Type[Any], parent_id: str, base_path: str, excluded_props: List[str] = [], only_props: Optional[List[str]] = None):
|
||||||
|
for name in dir(class_obj):
|
||||||
|
if name.startswith("_") or name in excluded_props:
|
||||||
|
continue
|
||||||
|
if only_props and name not in only_props:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(getattr(class_obj, name, None), property):
|
||||||
|
display_text = name
|
||||||
|
current_path = f"{base_path}.{name}"
|
||||||
|
if current_path in ENUM_REGISTRY:
|
||||||
|
display_text += " (Enum)"
|
||||||
|
self.fields_tree.insert(
|
||||||
|
parent_id, "end", iid=f"{parent_id}_{name}",
|
||||||
|
text=display_text, values=(name, current_path)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _load_profiles_to_combobox(self):
|
def _load_profiles_to_combobox(self):
|
||||||
@ -472,6 +371,8 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
self.profile_combobox["values"] = profile_names
|
self.profile_combobox["values"] = profile_names
|
||||||
if profile_names:
|
if profile_names:
|
||||||
self.selected_profile_name.set(profile_names[0])
|
self.selected_profile_name.set(profile_names[0])
|
||||||
|
else:
|
||||||
|
self.selected_profile_name.set("")
|
||||||
self._load_profile_into_ui()
|
self._load_profile_into_ui()
|
||||||
|
|
||||||
def _get_current_profile(self) -> Optional[ExportProfile]:
|
def _get_current_profile(self) -> Optional[ExportProfile]:
|
||||||
@ -483,64 +384,55 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
|
|
||||||
def _add_field(self):
|
def _add_field(self):
|
||||||
selected_item_id = self.fields_tree.focus()
|
selected_item_id = self.fields_tree.focus()
|
||||||
if not selected_item_id:
|
if not selected_item_id: return
|
||||||
return
|
|
||||||
item_values = self.fields_tree.item(selected_item_id, "values")
|
item_values = self.fields_tree.item(selected_item_id, "values")
|
||||||
if not item_values or len(item_values) < 2:
|
if not item_values or len(item_values) < 2:
|
||||||
messagebox.showinfo(
|
messagebox.showinfo( "Cannot Add Field", "Please select a specific data field, not a category.", parent=self)
|
||||||
"Cannot Add Field", "Please select a specific data field.", parent=self
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
tags = self.fields_tree.item(selected_item_id, "tags")
|
||||||
|
if "used_field" in tags:
|
||||||
|
messagebox.showinfo("Duplicate Field", "This field is already in the current profile.", parent=self)
|
||||||
|
return
|
||||||
|
|
||||||
column_name, data_path = item_values
|
column_name, data_path = item_values
|
||||||
profile = self._get_current_profile()
|
profile = self._get_current_profile()
|
||||||
if not profile:
|
if not profile: return
|
||||||
return
|
|
||||||
if any(f.data_path == data_path for f in profile.fields):
|
|
||||||
messagebox.showinfo(
|
|
||||||
"Duplicate Field", "This field is already in the profile.", parent=self
|
|
||||||
)
|
|
||||||
return
|
|
||||||
profile.fields.append(ExportField(column_name=column_name, data_path=data_path))
|
profile.fields.append(ExportField(column_name=column_name, data_path=data_path))
|
||||||
self._load_profile_into_ui()
|
self._load_profile_into_ui()
|
||||||
|
|
||||||
def _remove_field(self):
|
def _remove_field(self):
|
||||||
selection = self.selected_tree.selection()
|
selection = self.selected_tree.selection()
|
||||||
if not selection:
|
if not selection: return
|
||||||
return
|
|
||||||
index_to_remove = int(selection[0])
|
index_to_remove = int(selection[0])
|
||||||
profile = self._get_current_profile()
|
profile = self._get_current_profile()
|
||||||
if not profile:
|
if not profile: return
|
||||||
return
|
|
||||||
del profile.fields[index_to_remove]
|
del profile.fields[index_to_remove]
|
||||||
self._load_profile_into_ui()
|
self._load_profile_into_ui()
|
||||||
|
|
||||||
def _move_field(self, direction: int):
|
def _move_field(self, direction: int):
|
||||||
selection = self.selected_tree.selection()
|
selection = self.selected_tree.selection()
|
||||||
if not selection:
|
if not selection: return
|
||||||
return
|
|
||||||
index = int(selection[0])
|
index = int(selection[0])
|
||||||
new_index = index + direction
|
new_index = index + direction
|
||||||
profile = self._get_current_profile()
|
profile = self._get_current_profile()
|
||||||
if not profile or not (0 <= new_index < len(profile.fields)):
|
if not profile or not (0 <= new_index < len(profile.fields)): return
|
||||||
return
|
|
||||||
fields = profile.fields
|
fields = profile.fields
|
||||||
fields.insert(new_index, fields.pop(index))
|
fields.insert(new_index, fields.pop(index))
|
||||||
self._load_profile_into_ui()
|
self._load_profile_into_ui()
|
||||||
self.selected_tree.selection_set(str(new_index))
|
self.selected_tree.selection_set(str(new_index))
|
||||||
|
|
||||||
def _on_new_profile(self):
|
def _on_new_profile(self):
|
||||||
name = simpledialog.askstring(
|
name = simpledialog.askstring( "New Profile", "Enter a name for the new profile:", parent=self)
|
||||||
"New Profile", "Enter a name for the new profile:", parent=self
|
if not name or not name.strip(): return
|
||||||
)
|
|
||||||
if not name or not name.strip():
|
|
||||||
return
|
|
||||||
if any(p.name == name for p in self.profiles):
|
if any(p.name == name for p in self.profiles):
|
||||||
messagebox.showerror(
|
messagebox.showerror("Error", f"A profile with the name '{name}' already exists.", parent=self)
|
||||||
"Error",
|
|
||||||
f"A profile with the name '{name}' already exists.",
|
|
||||||
parent=self,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
new_profile = ExportProfile(name=name.strip())
|
new_profile = ExportProfile(name=name.strip())
|
||||||
self.profiles.append(new_profile)
|
self.profiles.append(new_profile)
|
||||||
self._load_profiles_to_combobox()
|
self._load_profiles_to_combobox()
|
||||||
@ -549,13 +441,8 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
|
|
||||||
def _on_delete_profile(self):
|
def _on_delete_profile(self):
|
||||||
profile = self._get_current_profile()
|
profile = self._get_current_profile()
|
||||||
if not profile:
|
if not profile: return
|
||||||
return
|
if messagebox.askyesno( "Confirm Delete", f"Are you sure you want to delete the profile '{profile.name}'?", parent=self):
|
||||||
if messagebox.askyesno(
|
|
||||||
"Confirm Delete",
|
|
||||||
f"Are you sure you want to delete the profile '{profile.name}'?",
|
|
||||||
parent=self,
|
|
||||||
):
|
|
||||||
self.profiles.remove(profile)
|
self.profiles.remove(profile)
|
||||||
self._load_profiles_to_combobox()
|
self._load_profiles_to_combobox()
|
||||||
|
|
||||||
@ -570,11 +457,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
|||||||
|
|
||||||
def _on_close(self):
|
def _on_close(self):
|
||||||
if self._check_unsaved_changes():
|
if self._check_unsaved_changes():
|
||||||
response = messagebox.askyesnocancel(
|
response = messagebox.askyesnocancel("Unsaved Changes", "You have unsaved changes. Would you like to save them?", parent=self)
|
||||||
"Unsaved Changes",
|
|
||||||
"You have unsaved changes. Would you like to save them?",
|
|
||||||
parent=self,
|
|
||||||
)
|
|
||||||
if response is True:
|
if response is True:
|
||||||
self._on_save_and_close()
|
self._on_save_and_close()
|
||||||
elif response is False:
|
elif response is False:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user