fix d1553 struct
This commit is contained in:
parent
f9d42677ac
commit
db0e26b5a1
@ -197,6 +197,21 @@
|
||||
"column_name": "scan_rate",
|
||||
"data_path": "main_header.ge_header.mode.scan_rate",
|
||||
"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",
|
||||
"data_path": "d1553_data.ground_track_angle_deg",
|
||||
"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,
|
||||
# AESA Block -> Synthetic Report
|
||||
"aesa_data.payload.comm": AesaError,
|
||||
# D1553
|
||||
"d1553_data.raw_mode": MasterMode,
|
||||
}
|
||||
|
||||
@ -231,6 +231,29 @@ class D1553Block(BaseBlock):
|
||||
self._ensure_cache()
|
||||
return self._cache.range_scale_nm
|
||||
|
||||
@property
|
||||
def raw_range_scale(self) -> Optional[int]:
|
||||
self._ensure_cache()
|
||||
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()
|
||||
|
||||
@ -5,7 +5,7 @@ from tkinter import ttk, simpledialog, messagebox
|
||||
import ctypes
|
||||
import copy
|
||||
import re
|
||||
import inspect # Import necessary for inspecting properties
|
||||
import inspect
|
||||
from typing import List, Type, Dict, Any, Optional
|
||||
|
||||
from .gui_utils import center_window
|
||||
@ -76,6 +76,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
self._init_window()
|
||||
self._init_vars()
|
||||
self._create_widgets()
|
||||
# Initial population of the tree
|
||||
self._populate_available_fields_tree()
|
||||
self._load_profiles_to_combobox()
|
||||
center_window(self, self.master)
|
||||
@ -118,6 +119,8 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
fields_frame.columnconfigure(0, weight=1)
|
||||
self.fields_tree = ttk.Treeview(fields_frame, selectmode="browse")
|
||||
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(
|
||||
fields_frame, orient="vertical", command=self.fields_tree.yview
|
||||
)
|
||||
@ -155,6 +158,10 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
show="headings",
|
||||
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("data_path", text="Source Path")
|
||||
self.selected_tree.heading("translate", text="Translate")
|
||||
@ -192,7 +199,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
dialog = EditPathDialog(self, initial_value=field.data_path)
|
||||
new_path = dialog.wait_for_input()
|
||||
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(
|
||||
"Invalid Path", "The path contains invalid characters.", parent=self
|
||||
)
|
||||
@ -205,7 +212,7 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
if region != "cell":
|
||||
return
|
||||
column_id = self.selected_tree.identify_column(event.x)
|
||||
if column_id != "#3":
|
||||
if column_id != "#3": # Only act on the "Translate" column
|
||||
return
|
||||
item_id = self.selected_tree.identify_row(event.y)
|
||||
if not item_id:
|
||||
@ -218,26 +225,33 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
base_path = re.sub(r"\[\d+\]", "", field.data_path)
|
||||
if base_path in ENUM_REGISTRY:
|
||||
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):
|
||||
# 1. Update the "Selected Fields" tree
|
||||
for i in self.selected_tree.get_children():
|
||||
self.selected_tree.delete(i)
|
||||
profile = self._get_current_profile()
|
||||
if not profile:
|
||||
self._update_available_fields_tree_tags() # Still update tags for empty profile
|
||||
return
|
||||
|
||||
for index, field in enumerate(profile.fields):
|
||||
base_path = re.sub(r"\[\d+\]", "", field.data_path)
|
||||
is_translatable = base_path in ENUM_REGISTRY
|
||||
checkbox_char = (
|
||||
"☑" if is_translatable and field.translate_with_enum else "☐"
|
||||
)
|
||||
checkbox_char = "☑" if field.translate_with_enum else "☐"
|
||||
|
||||
tag = "translatable" if is_translatable else "not_translatable"
|
||||
|
||||
self.selected_tree.insert(
|
||||
"",
|
||||
"end",
|
||||
iid=str(index),
|
||||
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):
|
||||
profile = self._get_current_profile()
|
||||
@ -253,184 +267,58 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
|
||||
def _populate_available_fields_tree(self):
|
||||
self.fields_tree.delete(*self.fields_tree.get_children())
|
||||
|
||||
# --- Helper to create a non-selectable root node ---
|
||||
def add_virtual_root(iid, text):
|
||||
return self.fields_tree.insert("", "end", iid=iid, text=text, open=False)
|
||||
|
||||
# --- Batch Properties (standalone fields) ---
|
||||
batch_root = self.fields_tree.insert(
|
||||
"", "end", iid="batch_properties", text="Batch Properties"
|
||||
)
|
||||
self.fields_tree.insert(
|
||||
batch_root,
|
||||
"end",
|
||||
iid="batch_id",
|
||||
text="batch_id",
|
||||
values=("batch_id", "batch_id"),
|
||||
)
|
||||
|
||||
# --- Batch Properties ---
|
||||
batch_root = add_virtual_root("batch_properties", "Batch Properties")
|
||||
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"])
|
||||
|
||||
# --- DSPHDRIN Data ---
|
||||
header_root = self.fields_tree.insert(
|
||||
"", "end", iid="header_data", text="Header Data (from DSPHDRIN)"
|
||||
)
|
||||
self._recursive_populate_tree_ctypes(
|
||||
ds.GeHeader, header_root, "main_header.ge_header"
|
||||
)
|
||||
header_root = add_virtual_root("header_data", "Header Data (from DSPHDRIN)")
|
||||
self._recursive_populate_tree_ctypes(ds.GeHeader, header_root, "main_header.ge_header")
|
||||
|
||||
# --- CDPSTS Data ---
|
||||
cdpsts_root = self.fields_tree.insert(
|
||||
"", "end", iid="cdpsts_data", text="CDP/STS Block Data"
|
||||
)
|
||||
self._recursive_populate_tree_ctypes(
|
||||
ds.CdpDataLayout, cdpsts_root, "cdp_sts_results.payload.data"
|
||||
)
|
||||
cdpsts_root = add_virtual_root("cdpsts_data", "CDP/STS Block Data")
|
||||
self._recursive_populate_tree_ctypes(ds.CdpStsPayload, cdpsts_root, "cdp_sts_results.payload")
|
||||
|
||||
# --- D1553 Data (with visual grouping) ---
|
||||
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_root = self.fields_tree.insert(
|
||||
"", "end", iid="timer_data", text="Timer Block Data"
|
||||
)
|
||||
self._recursive_populate_tree_ctypes(
|
||||
ds.GrifoTimerBlob, timer_root, "timer_data.blob"
|
||||
)
|
||||
timer_root = add_virtual_root("timer_data", "Timer Block Data")
|
||||
self._recursive_populate_tree_ctypes(ds.GrifoTimerBlob, timer_root, "timer_data.blob")
|
||||
|
||||
# --- AESA Data ---
|
||||
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"
|
||||
)
|
||||
# ... (add other blocks like AESA, EXP, etc. as needed, following this pattern) ...
|
||||
|
||||
def _update_available_fields_tree_tags(self):
|
||||
profile = self._get_current_profile()
|
||||
used_paths = {f.data_path for f in profile.fields} if profile else set()
|
||||
|
||||
# --- D1553 Data ---
|
||||
d1553_root = self.fields_tree.insert(
|
||||
"", "end", iid="d1553_data", text="D1553 Block Data"
|
||||
)
|
||||
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"
|
||||
)
|
||||
for item_id in self._get_all_tree_children(self.fields_tree, ""):
|
||||
item_values = self.fields_tree.item(item_id, "values")
|
||||
if item_values and len(item_values) >= 2:
|
||||
data_path = item_values[1]
|
||||
if data_path in used_paths:
|
||||
self.fields_tree.item(item_id, tags=("used_field",))
|
||||
else:
|
||||
self.fields_tree.item(item_id, tags=())
|
||||
|
||||
# --- EXPANDER Data ---
|
||||
exp_root = self.fields_tree.insert(
|
||||
"", "end", iid="exp_data", text="Expander Block Data"
|
||||
)
|
||||
# Add the raw_data_bytes field for Expander
|
||||
self.fields_tree.insert(
|
||||
exp_root,
|
||||
"end",
|
||||
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 ---
|
||||
pc_root = self.fields_tree.insert(
|
||||
"", "end", iid="pc_data", text="PC Block Data"
|
||||
)
|
||||
# Add the raw_data_bytes field for PC
|
||||
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 _get_all_tree_children(self, tree, item):
|
||||
children = tree.get_children(item)
|
||||
for child in children:
|
||||
yield child
|
||||
yield from self._get_all_tree_children(tree, child)
|
||||
|
||||
def _recursive_populate_tree_ctypes(
|
||||
self, class_obj: Type[ctypes.Structure], parent_id: str, base_path: str
|
||||
@ -440,38 +328,51 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
for field_name, field_type in class_obj._fields_:
|
||||
current_path = f"{base_path}.{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(
|
||||
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(
|
||||
field_type, child_node, current_path
|
||||
)
|
||||
elif hasattr(field_type, "_length_"):
|
||||
elif hasattr(field_type, "_length_"): # It's an array
|
||||
self.fields_tree.insert(
|
||||
parent_id,
|
||||
"end",
|
||||
iid=node_id,
|
||||
text=f"{field_name} [Array]",
|
||||
values=(field_name, current_path),
|
||||
parent_id, "end", 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}"
|
||||
if current_path in ENUM_REGISTRY:
|
||||
display_text += " (Enum)"
|
||||
self.fields_tree.insert(
|
||||
parent_id,
|
||||
"end",
|
||||
iid=node_id,
|
||||
text=display_text,
|
||||
values=(field_name, current_path),
|
||||
parent_id, "end", 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):
|
||||
profile_names = [p.name for p in self.profiles]
|
||||
self.profile_combobox["values"] = profile_names
|
||||
if profile_names:
|
||||
self.selected_profile_name.set(profile_names[0])
|
||||
else:
|
||||
self.selected_profile_name.set("")
|
||||
self._load_profile_into_ui()
|
||||
|
||||
def _get_current_profile(self) -> Optional[ExportProfile]:
|
||||
@ -483,64 +384,55 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
|
||||
def _add_field(self):
|
||||
selected_item_id = self.fields_tree.focus()
|
||||
if not selected_item_id:
|
||||
return
|
||||
if not selected_item_id: return
|
||||
|
||||
item_values = self.fields_tree.item(selected_item_id, "values")
|
||||
if not item_values or len(item_values) < 2:
|
||||
messagebox.showinfo(
|
||||
"Cannot Add Field", "Please select a specific data field.", parent=self
|
||||
)
|
||||
messagebox.showinfo( "Cannot Add Field", "Please select a specific data field, not a category.", parent=self)
|
||||
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
|
||||
profile = self._get_current_profile()
|
||||
if not profile:
|
||||
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
|
||||
if not profile: return
|
||||
|
||||
profile.fields.append(ExportField(column_name=column_name, data_path=data_path))
|
||||
self._load_profile_into_ui()
|
||||
|
||||
def _remove_field(self):
|
||||
selection = self.selected_tree.selection()
|
||||
if not selection:
|
||||
return
|
||||
if not selection: return
|
||||
index_to_remove = int(selection[0])
|
||||
profile = self._get_current_profile()
|
||||
if not profile:
|
||||
return
|
||||
if not profile: return
|
||||
|
||||
del profile.fields[index_to_remove]
|
||||
self._load_profile_into_ui()
|
||||
|
||||
def _move_field(self, direction: int):
|
||||
selection = self.selected_tree.selection()
|
||||
if not selection:
|
||||
return
|
||||
if not selection: return
|
||||
index = int(selection[0])
|
||||
new_index = index + direction
|
||||
profile = self._get_current_profile()
|
||||
if not profile or not (0 <= new_index < len(profile.fields)):
|
||||
return
|
||||
if not profile or not (0 <= new_index < len(profile.fields)): return
|
||||
|
||||
fields = profile.fields
|
||||
fields.insert(new_index, fields.pop(index))
|
||||
self._load_profile_into_ui()
|
||||
self.selected_tree.selection_set(str(new_index))
|
||||
|
||||
def _on_new_profile(self):
|
||||
name = simpledialog.askstring(
|
||||
"New Profile", "Enter a name for the new profile:", parent=self
|
||||
)
|
||||
if not name or not name.strip():
|
||||
return
|
||||
name = simpledialog.askstring( "New Profile", "Enter a name for the new profile:", parent=self)
|
||||
if not name or not name.strip(): return
|
||||
if any(p.name == name for p in self.profiles):
|
||||
messagebox.showerror(
|
||||
"Error",
|
||||
f"A profile with the name '{name}' already exists.",
|
||||
parent=self,
|
||||
)
|
||||
messagebox.showerror("Error", f"A profile with the name '{name}' already exists.", parent=self)
|
||||
return
|
||||
|
||||
new_profile = ExportProfile(name=name.strip())
|
||||
self.profiles.append(new_profile)
|
||||
self._load_profiles_to_combobox()
|
||||
@ -549,13 +441,8 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
|
||||
def _on_delete_profile(self):
|
||||
profile = self._get_current_profile()
|
||||
if not profile:
|
||||
return
|
||||
if messagebox.askyesno(
|
||||
"Confirm Delete",
|
||||
f"Are you sure you want to delete the profile '{profile.name}'?",
|
||||
parent=self,
|
||||
):
|
||||
if not profile: return
|
||||
if messagebox.askyesno( "Confirm Delete", f"Are you sure you want to delete the profile '{profile.name}'?", parent=self):
|
||||
self.profiles.remove(profile)
|
||||
self._load_profiles_to_combobox()
|
||||
|
||||
@ -570,14 +457,10 @@ class ProfileEditorWindow(tk.Toplevel):
|
||||
|
||||
def _on_close(self):
|
||||
if self._check_unsaved_changes():
|
||||
response = messagebox.askyesnocancel(
|
||||
"Unsaved Changes",
|
||||
"You have unsaved changes. Would you like to save them?",
|
||||
parent=self,
|
||||
)
|
||||
response = messagebox.askyesnocancel("Unsaved Changes", "You have unsaved changes. Would you like to save them?", parent=self)
|
||||
if response is True:
|
||||
self._on_save_and_close()
|
||||
elif response is False:
|
||||
self.destroy()
|
||||
else:
|
||||
self.destroy()
|
||||
self.destroy()
|
||||
Loading…
Reference in New Issue
Block a user