520 lines
24 KiB
Python
520 lines
24 KiB
Python
import tkinter as tk
|
||
from tkinter import ttk, scrolledtext
|
||
import sys
|
||
import json # Moved import to top
|
||
|
||
# Assuming RedirectText is in utils.py
|
||
from utils import RedirectText
|
||
from gui.element_mapping_frame import ElementMappingFrame
|
||
from gui.results_frame import ResultsFrame
|
||
from gui.log_frame import LogFrame
|
||
|
||
class NotebookManager(ttk.Notebook):
|
||
"""Manages the notebook tabs for the application."""
|
||
|
||
def __init__(self, app_instance, theme_manager, **kwargs):
|
||
"""
|
||
Initialize the Notebook Manager.
|
||
|
||
Args:
|
||
app_instance: The main SVGProcessorApp instance.
|
||
theme_manager: The theme manager instance.
|
||
**kwargs: Additional arguments for ttk.Notebook.
|
||
"""
|
||
super().__init__(app_instance.root, **kwargs)
|
||
self.app = app_instance # Store reference to the main app
|
||
self.theme_manager = theme_manager
|
||
self._create_tabs()
|
||
|
||
def _create_tabs(self):
|
||
"""Create the notebook tabs."""
|
||
# Element Mapping Tab
|
||
self.element_mapping_frame = ElementMappingFrame(self, save_config_callback=self._trigger_save)
|
||
self.add(self.element_mapping_frame, text='Element Mapping')
|
||
# self.theme_manager.configure_widget(self.element_mapping_frame)
|
||
|
||
# Results Tab
|
||
self.results_frame = ResultsFrame(self, export_command=self.app.create_scada_view)
|
||
self.add(self.results_frame, text='Results')
|
||
# self.theme_manager.configure_widget(self.results_frame)
|
||
|
||
# Logs Tab
|
||
self.log_frame = LogFrame(self)
|
||
self.add(self.log_frame, text='Logs')
|
||
# self.theme_manager.configure_widget(self.log_frame)
|
||
|
||
# Configure the notebook itself with the theme
|
||
# self.theme_manager.configure_widget(self)
|
||
|
||
def _trigger_save(self):
|
||
"""Placeholder or method to call the main app's save function if needed."""
|
||
# This might be called by ElementMappingFrame when changes occur.
|
||
# Check if the main app has a save method and call it.
|
||
if hasattr(self.app, '_save_config_from_ui'):
|
||
# print("NotebookManager triggering config save...") # Debug
|
||
# Schedule the save slightly later to avoid issues during trace callbacks
|
||
self.app.root.after_idle(self.app._save_config_from_ui)
|
||
else:
|
||
print("Warning: Main application has no _save_config_from_ui method to call.")
|
||
|
||
def get_element_mapping_frame(self):
|
||
"""Return the Element Mapping frame."""
|
||
return self.element_mapping_frame
|
||
|
||
def get_results_frame(self):
|
||
"""Return the Results frame."""
|
||
return self.results_frame
|
||
|
||
def get_log_frame(self):
|
||
"""Return the Log frame."""
|
||
return self.log_frame
|
||
|
||
def select_tab(self, index):
|
||
"""Select a specific tab in the notebook by index."""
|
||
if self:
|
||
try:
|
||
self.select(index)
|
||
except tk.TclError as e:
|
||
print(f"Error selecting notebook tab {index}: {e}")
|
||
|
||
def update_log(self, text):
|
||
"""Append text to the log display."""
|
||
if self.log_frame and self.log_frame.winfo_exists():
|
||
try:
|
||
self.log_frame.log_message(str(text))
|
||
except tk.TclError as e:
|
||
# print(f"Log update error: {e}") # Ignore if widget destroyed
|
||
pass
|
||
|
||
def update_results(self, elements):
|
||
"""Clear and display formatted JSON results in the results tab."""
|
||
if not self.results_frame or not self.results_frame.winfo_exists():
|
||
print("Warning: Results display widget not available.")
|
||
return
|
||
|
||
self.results_frame.set_results("")
|
||
if not elements:
|
||
self.results_frame.set_results("No elements found matching the criteria.")
|
||
return
|
||
|
||
try:
|
||
# import json # Moved to top
|
||
formatted_json = json.dumps(elements, indent=2)
|
||
|
||
# Simple insertion for now, add chunking if needed later
|
||
self.results_frame.set_results(formatted_json)
|
||
|
||
except TypeError as te:
|
||
error_msg = f"Result Display Error: Data could not be serialized to JSON.\n{te}\n\nData Preview:\n{str(elements)[:500]}..."
|
||
print(error_msg)
|
||
self.results_frame.set_results(error_msg)
|
||
except Exception as e:
|
||
error_msg = f"Unexpected error displaying results: {e}"
|
||
print(error_msg)
|
||
self.results_frame.set_results(error_msg)
|
||
|
||
def get_results_text(self):
|
||
"""Get the current text content from the results tab."""
|
||
if self.results_frame and self.results_frame.winfo_exists():
|
||
return self.results_frame.get_results().strip()
|
||
return ""
|
||
|
||
def clear_results(self):
|
||
"""Clear the results text widget."""
|
||
if self.results_frame and self.results_frame.winfo_exists():
|
||
self.results_frame.set_results("")
|
||
|
||
def start_progress(self, speed=10):
|
||
"""Start the indeterminate progress bar."""
|
||
if self.element_mapping_frame and self.element_mapping_frame.progress:
|
||
self.element_mapping_frame.progress.start(speed)
|
||
|
||
def stop_progress(self):
|
||
"""Stop the progress bar."""
|
||
if self.element_mapping_frame and self.element_mapping_frame.progress:
|
||
self.element_mapping_frame.progress.stop()
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Element Mapping Tab Methods
|
||
# ----------------------------------------------------------------------
|
||
|
||
def _create_element_mapping_tab(self, parent_frame):
|
||
"""Create the content for the Element Mapping tab, including a scrollable grid."""
|
||
# Main frame for the tab content
|
||
mapping_content_frame = ttk.Frame(parent_frame)
|
||
mapping_content_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# --- Top Info/Controls ---
|
||
top_frame = ttk.Frame(mapping_content_frame)
|
||
top_frame.pack(fill=tk.X, pady=(5, 10))
|
||
ttk.Label(top_frame, text="Element Mapping Configuration", style="Header.TLabel").pack(side=tk.LEFT, anchor=tk.W)
|
||
|
||
# --- Scrollable Table Frame ---
|
||
table_container = ttk.Frame(mapping_content_frame)
|
||
table_container.pack(fill=tk.BOTH, expand=True)
|
||
|
||
canvas = tk.Canvas(table_container, borderwidth=0, highlightthickness=0) # No border for canvas
|
||
scrollbar = ttk.Scrollbar(table_container, orient="vertical", command=canvas.yview)
|
||
# Frame inside canvas holds the actual grid of widgets
|
||
self.mapping_frame = ttk.Frame(canvas, padding=(5, 5)) # Padding inside grid frame
|
||
|
||
canvas.configure(yscrollcommand=scrollbar.set)
|
||
canvas.pack(side="left", fill="both", expand=True)
|
||
scrollbar.pack(side="right", fill="y")
|
||
|
||
canvas_frame_id = canvas.create_window((0, 0), window=self.mapping_frame, anchor="nw")
|
||
|
||
# Update scroll region when the inner frame's size changes
|
||
def on_frame_configure(event=None):
|
||
canvas.configure(scrollregion=canvas.bbox("all"))
|
||
|
||
# Update the width of the inner frame when the canvas width changes
|
||
def on_canvas_configure(event):
|
||
canvas.itemconfig(canvas_frame_id, width=event.width)
|
||
|
||
self.mapping_frame.bind("<Configure>", on_frame_configure)
|
||
canvas.bind('<Configure>', on_canvas_configure)
|
||
|
||
# Bind mouse wheel scrolling to the canvas (platform specific)
|
||
self._bind_mousewheel(canvas)
|
||
|
||
# --- Grid Headers (inside self.mapping_frame) ---
|
||
headers = [
|
||
("SVG Type", 10), ("Label Prefix", 10), ("Output Type", 20),
|
||
("Props Path", 30), ("Size (WxH)", 12), ("Offset (X,Y)", 12),
|
||
("Final Prefix", 15), ("Final Suffix", 15), ("Del", 3)
|
||
]
|
||
for col, (text, _) in enumerate(headers):
|
||
header_label = ttk.Label(self.mapping_frame, text=text, font=('Helvetica', 9, 'bold'), anchor=tk.W)
|
||
header_label.grid(row=0, column=col, sticky=tk.EW, pady=(0, 5), padx=3)
|
||
# Optional: Configure column weights here if needed
|
||
if text == "Output Type": self.mapping_frame.columnconfigure(col, weight=1)
|
||
if text == "Props Path": self.mapping_frame.columnconfigure(col, weight=2)
|
||
|
||
# --- Add Button (outside scrollable area) ---
|
||
self.add_button_frame = ttk.Frame(mapping_content_frame)
|
||
self.add_button_frame.pack(fill=tk.X, pady=(10, 5)) # Below scrollable area
|
||
ttk.Button(self.add_button_frame, text=" + Add Mapping ", command=self._handle_add_mapping_click).pack(side=tk.LEFT, padx=5)
|
||
|
||
# Initialize mapping rows list here if not done in __init__
|
||
if not hasattr(self, 'mapping_rows'): self.mapping_rows = []
|
||
|
||
def _bind_mousewheel(self, widget):
|
||
"""Bind mouse wheel events for scrolling across platforms."""
|
||
if sys.platform == "linux":
|
||
widget.bind("<Button-4>", lambda e: self._on_mousewheel(e, widget, -1), add='+')
|
||
widget.bind("<Button-5>", lambda e: self._on_mousewheel(e, widget, 1), add='+')
|
||
else: # Windows and macOS
|
||
widget.bind("<MouseWheel>", lambda e: self._on_mousewheel(e, widget), add='+')
|
||
|
||
def _on_mousewheel(self, event, canvas, direction=None):
|
||
"""Handle mouse wheel scroll events for the canvas."""
|
||
if direction: # Linux Button-4/5
|
||
delta = direction
|
||
else: # Windows/macOS MouseWheel
|
||
# Determine scroll direction and magnitude (platform dependent)
|
||
if sys.platform == "darwin": # macOS may need adjustment
|
||
delta = -1 * event.delta
|
||
else: # Windows
|
||
delta = -1 * (event.delta // 120) # Windows delta is typically +/- 120
|
||
canvas.yview_scroll(delta, "units")
|
||
|
||
def _handle_add_mapping_click(self):
|
||
"""Callback for the 'Add New Mapping' button."""
|
||
original_allow_empty = self.element_mapping_frame.allow_empty_rows
|
||
self.element_mapping_frame.allow_empty_rows = True
|
||
new_index = self.add_mapping_row() # Add blank row
|
||
self.element_mapping_frame.allow_empty_rows = original_allow_empty
|
||
|
||
# Focus and scroll
|
||
if 0 <= new_index < len(self.mapping_rows):
|
||
try:
|
||
self.mapping_rows[new_index]['svg_entry'].focus_set()
|
||
except (KeyError, tk.TclError): pass
|
||
self._scroll_mapping_to_bottom()
|
||
|
||
def _scroll_mapping_to_bottom(self):
|
||
"""Scrolls the element mapping canvas to the bottom."""
|
||
self.theme_manager.update_scroll_region(self.element_mapping_frame.mapping_frame)
|
||
|
||
def add_mapping_row(self, data=None):
|
||
"""Adds a mapping row to the UI, optionally populated with data."""
|
||
if data is None: data = {}
|
||
row_index = len(self.mapping_rows) + 1 # Grid row below header
|
||
|
||
# --- Data Variables ---
|
||
row_vars = {
|
||
'svg_type': tk.StringVar(value=data.get('svg_type', '')),
|
||
'label_prefix': tk.StringVar(value=data.get('label_prefix', '')),
|
||
'element_type': tk.StringVar(value=data.get('element_type', '')),
|
||
'props_path': tk.StringVar(value=data.get('props_path', '')),
|
||
'width': tk.StringVar(value=str(data.get('width', ''))),
|
||
'height': tk.StringVar(value=str(data.get('height', ''))),
|
||
'x_offset': tk.StringVar(value=str(data.get('x_offset', ''))),
|
||
'y_offset': tk.StringVar(value=str(data.get('y_offset', ''))),
|
||
'final_prefix': tk.StringVar(value=data.get('final_prefix', '')),
|
||
'final_suffix': tk.StringVar(value=data.get('final_suffix', '')),
|
||
}
|
||
# Trigger config save on change
|
||
save_callback = self.element_mapping_frame.mapping_callbacks.get('save_config', lambda *a: None)
|
||
for var in row_vars.values():
|
||
var.trace_add("write", lambda *a, cb=save_callback: self._schedule_config_save(cb))
|
||
|
||
# --- Widgets ---
|
||
entries = {}
|
||
col = 0
|
||
pad_x = 3
|
||
pad_y = 1
|
||
entries['svg_entry'] = ttk.Entry(self.mapping_frame, textvariable=row_vars['svg_type'], width=10)
|
||
entries['svg_entry'].grid(row=row_index, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
|
||
entries['label_prefix_entry'] = ttk.Entry(self.mapping_frame, textvariable=row_vars['label_prefix'], width=10)
|
||
entries['label_prefix_entry'].grid(row=row_index, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
|
||
entries['element_entry'] = ttk.Entry(self.mapping_frame, textvariable=row_vars['element_type'], width=20)
|
||
entries['element_entry'].grid(row=row_index, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
|
||
entries['props_entry'] = ttk.Entry(self.mapping_frame, textvariable=row_vars['props_path'], width=30)
|
||
entries['props_entry'].grid(row=row_index, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
|
||
# Size Frame
|
||
size_frame = ttk.Frame(self.mapping_frame)
|
||
size_frame.grid(row=row_index, column=col, sticky=tk.EW, pady=0, padx=0); col+=1
|
||
entries['width_entry'] = ttk.Entry(size_frame, textvariable=row_vars['width'], width=4)
|
||
entries['width_entry'].pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(pad_x,1))
|
||
ttk.Label(size_frame, text="×").pack(side=tk.LEFT)
|
||
entries['height_entry'] = ttk.Entry(size_frame, textvariable=row_vars['height'], width=4)
|
||
entries['height_entry'].pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(1,pad_x))
|
||
entries['size_frame'] = size_frame
|
||
|
||
# Offset Frame
|
||
offset_frame = ttk.Frame(self.mapping_frame)
|
||
offset_frame.grid(row=row_index, column=col, sticky=tk.EW, pady=0, padx=0); col+=1
|
||
entries['x_offset_entry'] = ttk.Entry(offset_frame, textvariable=row_vars['x_offset'], width=4)
|
||
entries['x_offset_entry'].pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(pad_x,1))
|
||
ttk.Label(offset_frame, text=",").pack(side=tk.LEFT)
|
||
entries['y_offset_entry'] = ttk.Entry(offset_frame, textvariable=row_vars['y_offset'], width=4)
|
||
entries['y_offset_entry'].pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(1,pad_x))
|
||
entries['offset_frame'] = offset_frame
|
||
|
||
# Final Prefix/Suffix
|
||
entries['final_prefix_entry'] = ttk.Entry(self.mapping_frame, textvariable=row_vars['final_prefix'], width=15)
|
||
entries['final_prefix_entry'].grid(row=row_index, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
entries['final_suffix_entry'] = ttk.Entry(self.mapping_frame, textvariable=row_vars['final_suffix'], width=15)
|
||
entries['final_suffix_entry'].grid(row=row_index, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
|
||
# Remove Button
|
||
current_list_index = len(self.mapping_rows)
|
||
remove_button = ttk.Button(self.mapping_frame, text="×", width=2, style="Danger.TButton",
|
||
command=lambda idx=current_list_index: self._handle_remove_mapping_click(idx))
|
||
remove_button.grid(row=row_index, column=col, sticky=tk.W, pady=0, padx=pad_x); col+=1
|
||
entries['remove_button'] = remove_button
|
||
|
||
# Store row data
|
||
row_data_dict = {**row_vars, **entries, 'grid_row': row_index}
|
||
self.mapping_rows.append(row_data_dict)
|
||
|
||
# Schedule save if needed
|
||
if self.element_mapping_frame.initialized_mappings:
|
||
if self.element_mapping_frame.skip_next_save:
|
||
self.element_mapping_frame.skip_next_save = False
|
||
else:
|
||
self._schedule_config_save(save_callback, delay=100)
|
||
|
||
# Update canvas scroll region
|
||
self._update_mapping_scrollregion()
|
||
|
||
return current_list_index
|
||
|
||
def _handle_remove_mapping_click(self, index_in_list):
|
||
"""Callback to remove a specific mapping row."""
|
||
self.remove_mapping_row(index_in_list)
|
||
save_callback = self.element_mapping_frame.mapping_callbacks.get('save_config', lambda: None)
|
||
self._schedule_config_save(save_callback, delay=100) # Save quickly after removal
|
||
|
||
def remove_mapping_row(self, index_in_list):
|
||
"""Removes the mapping row widgets and data at the given list index."""
|
||
if not (0 <= index_in_list < len(self.mapping_rows)):
|
||
return
|
||
|
||
row_to_remove = self.mapping_rows[index_in_list]
|
||
|
||
# Destroy Widgets
|
||
widgets_to_destroy = [
|
||
'svg_entry', 'label_prefix_entry', 'element_entry', 'props_entry',
|
||
'width_entry', 'height_entry', 'x_offset_entry', 'y_offset_entry',
|
||
'final_prefix_entry', 'final_suffix_entry', 'remove_button',
|
||
'size_frame', 'offset_frame'
|
||
]
|
||
for key in widgets_to_destroy:
|
||
widget = row_to_remove.get(key)
|
||
if widget and isinstance(widget, tk.Widget):
|
||
try: widget.destroy()
|
||
except tk.TclError: pass
|
||
|
||
# Remove data from list
|
||
self.mapping_rows.pop(index_in_list)
|
||
|
||
# Re-grid remaining rows and update commands
|
||
self._reindex_mapping_rows()
|
||
|
||
# Handle empty list case
|
||
if not self.mapping_rows:
|
||
self.element_mapping_frame.skip_next_save = True
|
||
self.add_mapping_row() # Add default row
|
||
|
||
# Update scroll region
|
||
self._update_mapping_scrollregion()
|
||
|
||
def _reindex_mapping_rows(self):
|
||
"""Update grid rows and remove button commands after list changes."""
|
||
for i, row_data in enumerate(self.mapping_rows):
|
||
new_grid_row = i + 1
|
||
col = 0
|
||
pad_x = 3
|
||
pad_y = 1
|
||
|
||
# Grid widgets (ensure they exist)
|
||
row_data.get('svg_entry', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
row_data.get('label_prefix_entry', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
row_data.get('element_entry', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
row_data.get('props_entry', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
row_data.get('size_frame', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=0, padx=0); col+=1
|
||
row_data.get('offset_frame', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=0, padx=0); col+=1
|
||
row_data.get('final_prefix_entry', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
row_data.get('final_suffix_entry', tk.Widget()).grid(row=new_grid_row, column=col, sticky=tk.EW, pady=pad_y, padx=pad_x); col+=1
|
||
remove_button = row_data.get('remove_button', None)
|
||
if remove_button: remove_button.grid(row=new_grid_row, column=col, sticky=tk.W, pady=0, padx=pad_x); col+=1
|
||
|
||
# Update Remove Button Command
|
||
if remove_button and isinstance(remove_button, ttk.Button):
|
||
remove_button.configure(command=lambda idx=i: self._handle_remove_mapping_click(idx))
|
||
|
||
row_data['grid_row'] = new_grid_row
|
||
|
||
def _update_mapping_scrollregion(self):
|
||
"""Forces an update of the mapping canvas scroll region."""
|
||
self.theme_manager.update_scroll_region(self.element_mapping_frame.mapping_frame)
|
||
|
||
def _schedule_config_save(self, save_callback, delay=1500):
|
||
"""Debounces calls to the save configuration callback."""
|
||
if self._save_timer_id is not None:
|
||
self.theme_manager.after_cancel(self._save_timer_id)
|
||
# print(f"DEBUG: Scheduling save in {delay}ms") # Debug
|
||
self._save_timer_id = self.theme_manager.after(delay, save_callback)
|
||
|
||
def load_element_mappings(self, mappings_data):
|
||
"""Clears existing mapping rows and loads new ones from data."""
|
||
# Clear existing UI rows
|
||
for i in range(len(self.mapping_rows) - 1, -1, -1):
|
||
self.remove_mapping_row(i)
|
||
|
||
# Load new mappings
|
||
if isinstance(mappings_data, list) and mappings_data:
|
||
for mapping_dict in mappings_data:
|
||
if isinstance(mapping_dict, dict):
|
||
self.add_mapping_row(data=mapping_dict)
|
||
|
||
# Ensure at least one row exists
|
||
if not self.mapping_rows:
|
||
self.element_mapping_frame.skip_next_save = True
|
||
self.add_mapping_row() # Add default blank row
|
||
|
||
self.element_mapping_frame.initialized_mappings = True
|
||
self._update_mapping_scrollregion()
|
||
|
||
def get_element_mappings(self):
|
||
"""Extracts the current element mapping configuration from the UI."""
|
||
mappings_to_return = []
|
||
if not hasattr(self, 'mapping_rows'): return mappings_to_return
|
||
|
||
for row_data in self.mapping_rows:
|
||
try:
|
||
svg_type = row_data['svg_type'].get().strip()
|
||
element_type = row_data['element_type'].get().strip()
|
||
# Only include valid rows (must have svg_type and element_type)
|
||
if not svg_type or not element_type:
|
||
continue
|
||
|
||
mapping = {
|
||
'svg_type': svg_type,
|
||
'element_type': element_type,
|
||
'label_prefix': row_data['label_prefix'].get().strip(),
|
||
'props_path': row_data['props_path'].get().strip(),
|
||
'final_prefix': row_data['final_prefix'].get().strip(),
|
||
'final_suffix': row_data['final_suffix'].get().strip(),
|
||
}
|
||
# Convert numeric, handle errors gracefully (defaulting)
|
||
for key, default in [('width', 14), ('height', 14), ('x_offset', 0), ('y_offset', 0)]:
|
||
val_str = row_data[key].get().strip()
|
||
try: mapping[key] = int(val_str) if val_str else default
|
||
except ValueError: mapping[key] = default
|
||
|
||
mappings_to_return.append(mapping)
|
||
except (KeyError, AttributeError, tk.TclError) as e:
|
||
print(f"Warning: Skipping invalid mapping row during get: {e}")
|
||
continue
|
||
return mappings_to_return
|
||
|
||
def cleanup_incomplete_mappings(self):
|
||
"""Removes mapping rows where essential fields are empty."""
|
||
if not hasattr(self, 'mapping_rows'): return
|
||
|
||
indices_to_remove = []
|
||
for i in range(len(self.mapping_rows) - 1, -1, -1):
|
||
row_data = self.mapping_rows[i]
|
||
try:
|
||
svg_type = row_data.get('svg_type', tk.StringVar()).get().strip()
|
||
element_type = row_data.get('element_type', tk.StringVar()).get().strip()
|
||
# Remove if essential field is empty, unless it's the only row left
|
||
if (not svg_type or not element_type) and len(self.mapping_rows) > 1:
|
||
indices_to_remove.append(i)
|
||
except (AttributeError, KeyError, tk.TclError):
|
||
indices_to_remove.append(i) # Assume broken row
|
||
|
||
if indices_to_remove:
|
||
# Disable saving during cleanup
|
||
if self._save_timer_id:
|
||
self.theme_manager.after_cancel(self._save_timer_id)
|
||
self._save_timer_id = None
|
||
# Remove rows
|
||
for index in indices_to_remove:
|
||
self.remove_mapping_row(index)
|
||
|
||
# Example Usage
|
||
if __name__ == '__main__':
|
||
root = tk.Tk()
|
||
root.title("Notebook Manager Test")
|
||
root.geometry("800x600")
|
||
|
||
# Mock ThemeManager for testing
|
||
class MockThemeManager:
|
||
def configure_widget(self, widget):
|
||
# Apply some basic styling for visibility
|
||
try:
|
||
widget.config(style='TFrame' if isinstance(widget, ttk.Frame) else 'TNotebook')
|
||
except tk.TclError:
|
||
pass # Ignore if style doesn't apply
|
||
def toggle_theme(self): pass
|
||
def apply_theme(self): pass
|
||
def update_scroll_region(self, widget): pass
|
||
def after_cancel(self, timer_id): pass
|
||
def after(self, delay, callback): return 1 # Mock implementation
|
||
|
||
style = ttk.Style()
|
||
style.theme_use('clam')
|
||
style.configure('TNotebook', background='#f0f0f0', tabmargins=[2, 5, 2, 0])
|
||
style.configure('TNotebook.Tab', padding=[5, 2], font=('Arial', 10))
|
||
style.configure('TFrame', background='#ffffff')
|
||
|
||
mock_theme_manager = MockThemeManager()
|
||
|
||
notebook_manager = NotebookManager(root, mock_theme_manager)
|
||
notebook_manager.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||
|
||
# You can access the frames like this:
|
||
# notebook_manager.get_results_frame().set_results("Sample results text")
|
||
# notebook_manager.get_log_frame().log_message("Sample log message")
|
||
# mappings = notebook_manager.get_element_mapping_frame().get_mappings()
|
||
|
||
root.mainloop() |