import tkinter as tk from tkinter import ttk, messagebox import re import json from uuid import uuid4 class ReverseMoralithicChainSetValidatorMode5: def __init__(self, root): self.root = root self.root.title("Reverse Moralithic Chain Set Validator - Mode 5") self.valid_statement_source_pairs = { 'ST31': 'SO30', 'ST32': 'SO31', 'ST33': 'SO32', 'ST34': 'SO33', 'ST43': 'SO42', 'ST44': 'SO43', 'ST45': 'SO44', 'ST46': 'SO45', 'ST47': 'SO46', 'ST48': 'SO47', 'ST49': 'SO48', 'ST50': 'SO49', 'ST52': 'SO51', 'ST54': 'SO53', 'ST56': 'SO55', 'ST58': 'SO57', 'ST59': 'SO58', 'ST60': 'SO59', 'ST61': 'SO60', 'ST62': 'SO61', 'ST63': 'SO62', 'ST64': 'SO63', 'ST65': 'SO64', 'ST66': 'SO65', 'ST67': 'SO66', 'ST68': 'SO67', 'ST69': 'SO68', 'ST70': 'SO69', 'ST71': 'SO70', 'ST72': 'SO71', 'ST73': 'SO72', 'ST74': 'SO73' } self.valid_statement_value_pairs = { 'ST31': {'values': ['VL31'], 'required': 'VL31'}, 'ST32': {'values': ['VL31', 'VL32'], 'required': 'VL32'}, 'ST33': {'values': ['VL33'], 'required': 'VL33'}, 'ST34': {'values': ['VL31', 'VL32', 'VL33', 'VL34'], 'required': 'VL34'}, 'ST43': {'values': ['VL43'], 'required': 'VL43'}, 'ST44': {'values': ['VL43', 'VL44'], 'required': 'VL44'}, 'ST45': {'values': ['VL45'], 'required': 'VL45'}, 'ST46': {'values': ['VL43', 'VL44', 'VL45', 'VL46'], 'required': 'VL46'}, 'ST47': {'values': ['VL47'], 'required': 'VL47'}, 'ST48': {'values': ['VL47', 'VL48'], 'required': 'VL48'}, 'ST49': {'values': ['VL47', 'VL48', 'VL49'], 'required': 'VL49'}, 'ST50': {'values': ['VL47', 'VL48', 'VL49', 'VL50'], 'required': 'VL50'}, 'ST52': {'values': ['VL52'], 'required': 'VL52'}, 'ST54': {'values': ['VL54'], 'required': 'VL54'}, 'ST56': {'values': ['VL56'], 'required': 'VL56'}, 'ST58': {'values': ['VL58'], 'required': 'VL58'}, 'ST59': {'values': ['VL59'], 'required': 'VL59'}, 'ST60': {'values': ['VL59', 'VL60'], 'required': 'VL60'}, 'ST61': {'values': ['VL61'], 'required': 'VL61'}, 'ST62': {'values': ['VL59', 'VL60', 'VL61', 'VL62'], 'required': 'VL62'}, 'ST63': {'values': ['VL63'], 'required': 'VL63'}, 'ST64': {'values': ['VL63', 'VL64'], 'required': 'VL64'}, 'ST65': {'values': ['VL65'], 'required': 'VL65'}, 'ST66': {'values': ['VL63', 'VL64', 'VL65', 'VL66'], 'required': 'VL66'}, 'ST67': {'values': ['VL67'], 'required': 'VL67'}, 'ST68': {'values': ['VL67', 'VL68'], 'required': 'VL68'}, 'ST69': {'values': ['VL69'], 'required': 'VL69'}, 'ST70': {'values': ['VL67', 'VL68', 'VL69', 'VL70'], 'required': 'VL70'}, 'ST71': {'values': ['VL71'], 'required': 'VL71'}, 'ST72': {'values': ['VL71', 'VL72'], 'required': 'VL72'}, 'ST73': {'values': ['VL73'], 'required': 'VL73'}, 'ST74': {'values': ['VL71', 'VL72', 'VL73', 'VL74'], 'required': 'VL74'} } self.valid_interaction_tags = ( [f"NI0{i}" for i in range(4, 8)] + [f"EF{i}" for i in range(32, 64)] + [f"RP0{i}" for i in range(4, 8)] + [f"RE{i:02d}" for i in range(8, 16)] + [f"AC{i:02d}" for i in range(8, 16)] ) self.valid_means_tags = ["ME02", "ME03"] self.valid_displayment_tags = ["DP02", "DP03"] self.valid_set_types = ["Non-Interactor", "Enforcer", "Repeater", "Rejecter", "Accepter"] self.conversion_rules = { 'AC08': ['RE08', 'RE12'], 'AC09': ['RE09', 'RE13'], 'AC10': ['RE10', 'RE14'], 'AC11': ['RE11', 'RE15'], 'AC12': ['RE08', 'RE12'], 'AC13': ['RE09', 'RE13'], 'AC14': ['RE10', 'RE14'], 'AC15': ['RE11', 'RE15'], 'RE08': ['AC08', 'AC12'], 'RE09': ['AC09', 'AC13'], 'RE10': ['AC10', 'AC14'], 'RE11': ['AC11', 'AC15'], 'RE12': ['AC08', 'AC12'], 'RE13': ['AC09', 'AC13'], 'RE14': ['AC10', 'AC14'], 'RE15': ['AC11', 'AC15'] } self.transformation_rules = { 'NI04': ['NI06'], 'NI05': ['NI07'], 'NI06': ['NI04'], 'NI07': ['NI05'], 'EF32': ['EF34', 'EF36', 'EF38'], 'EF33': ['EF35', 'EF37', 'EF39'], 'EF34': ['EF32', 'EF36', 'EF38'], 'EF35': ['EF33', 'EF37', 'EF39'], 'EF36': ['EF32', 'EF34', 'EF38'], 'EF37': ['EF33', 'EF35', 'EF39'], 'EF38': ['EF32', 'EF34', 'EF36'], 'EF39': ['EF33', 'EF35', 'EF37'], 'EF40': ['EF42', 'EF44', 'EF46'], 'EF41': ['EF43', 'EF45', 'EF47'], 'EF42': ['EF40', 'EF44', 'EF46'], 'EF43': ['EF41', 'EF45', 'EF47'], 'EF44': ['EF40', 'EF42', 'EF46'], 'EF45': ['EF41', 'EF43', 'EF47'], 'EF46': ['EF40', 'EF42', 'EF44'], 'EF47': ['EF41', 'EF43', 'EF45'], 'EF48': ['EF50', 'EF52', 'EF54'], 'EF49': ['EF51', 'EF53', 'EF55'], 'EF50': ['EF48', 'EF52', 'EF54'], 'EF51': ['EF49', 'EF53', 'EF55'], 'EF52': ['EF48', 'EF50', 'EF54'], 'EF53': ['EF49', 'EF51', 'EF55'], 'EF54': ['EF48', 'EF50', 'EF52'], 'EF55': ['EF49', 'EF51', 'EF53'], 'EF56': ['EF58', 'EF60', 'EF62'], 'EF57': ['EF59', 'EF61', 'EF63'], 'EF58': ['EF56', 'EF60', 'EF62'], 'EF59': ['EF57', 'EF61', 'EF63'], 'EF60': ['EF56', 'EF58', 'EF62'], 'EF61': ['EF57', 'EF59', 'EF63'], 'EF62': ['EF56', 'EF58', 'EF60'], 'EF63': ['EF57', 'EF59', 'EF61'], 'RE08': ['RE12'], 'RE09': ['RE13'], 'RE10': ['RE14'], 'RE11': ['RE15'], 'RE12': ['RE08'], 'RE13': ['RE09'], 'RE14': ['RE10'], 'RE15': ['RE11'], 'AC08': ['AC12'], 'AC09': ['AC13'], 'AC10': ['AC14'], 'AC11': ['AC15'], 'AC12': ['AC08'], 'AC13': ['AC09'], 'AC14': ['AC10'], 'AC15': ['AC11'], 'RP04': [], 'RP05': [], 'RP06': [], 'RP07': [] } self.forward_conversion_rules = {} for current_tag, prev_list in self.conversion_rules.items(): for prev_tag in prev_list: if prev_tag not in self.forward_conversion_rules: self.forward_conversion_rules[prev_tag] = [] self.forward_conversion_rules[prev_tag].append(current_tag) self.forward_transformation_rules = {} for current_tag, prev_list in self.transformation_rules.items(): for prev_tag in prev_list: if prev_tag not in self.forward_transformation_rules: self.forward_transformation_rules[prev_tag] = [] self.forward_transformation_rules[prev_tag].append(current_tag) self.tabs = {} self.sets = {} self.setup_gui() def setup_gui(self): canvas_frame = ttk.Frame(self.root) canvas_frame.pack(fill=tk.BOTH, expand=True) self.canvas = tk.Canvas(canvas_frame) v_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.VERTICAL, command=self.canvas.yview) self.canvas.configure(yscrollcommand=v_scrollbar.set) v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.main_frame = ttk.Frame(self.canvas) self.canvas.create_window((0, 0), window=self.main_frame, anchor="nw") self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) self.main_frame.bind("", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) def on_mousewheel(event): if event.delta: self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") elif event.num == 4: self.canvas.yview_scroll(-1, "units") elif event.num == 5: self.canvas.yview_scroll(1, "units") self.canvas.bind_all("", on_mousewheel) self.canvas.bind_all("", on_mousewheel) self.canvas.bind_all("", on_mousewheel) button_frame = ttk.Frame(self.main_frame) button_frame.pack(fill=tk.X, pady=5) ttk.Button(button_frame, text="Add Set", command=self.add_set).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="Add Tab", command=self.add_tab).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="Validate Tab", command=self.validate_tab).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="Save Set", command=self.save_set).pack(side=tk.LEFT, padx=5) self.notebook = ttk.Notebook(self.main_frame) self.notebook.pack(fill=tk.BOTH, expand=True, pady=5) self.add_tab() def add_tab(self): if len(self.tabs) >= 5: messagebox.showerror("Error", "Maximum of 5 tabs reached.") return entity_window = tk.Toplevel(self.root) entity_window.title("Assign Entity to Tab") entity_window.geometry("300x150") ttk.Label(entity_window, text="Enter Entity Name:").pack(pady=5) entity_var = tk.StringVar() ttk.Entry(entity_window, textvariable=entity_var).pack(pady=5) def confirm_entity(): entity = entity_var.get().strip() if not entity: messagebox.showerror("Error", "Please enter an entity name.") return tab_id = str(uuid4()) tab_name = f"Tab {len(self.tabs) + 1} ({entity})" tab_frame = ttk.Frame(self.notebook) self.notebook.add(tab_frame, text=tab_name) self.tabs[tab_id] = {"name": tab_name, "entity": entity} self.sets[tab_id] = {} entity_window.destroy() self.canvas.configure(scrollregion=self.canvas.bbox("all")) ttk.Button(entity_window, text="Confirm", command=confirm_entity).pack(pady=5) def add_set(self): current_tab = self.notebook.select() if not current_tab: messagebox.showerror("Error", "No tab selected.") return tab_id = list(self.tabs.keys())[self.notebook.index(current_tab)] if len(self.sets[tab_id]) >= 5: messagebox.showerror("Error", "Maximum of 5 sets per tab reached.") return set_type_window = tk.Toplevel(self.root) set_type_window.title("Configure New Set") set_type_window.geometry("300x200") ttk.Label(set_type_window, text="Select Set Type:").pack(pady=5) set_type_var = tk.StringVar() set_type_combobox = ttk.Combobox(set_type_window, textvariable=set_type_var, values=[t for t in self.valid_set_types if t not in self.sets[tab_id]], state="readonly") set_type_combobox.pack(pady=5) ttk.Label(set_type_window, text="Select Set Mode:").pack(pady=5) mode_var = tk.StringVar(value="weight") ttk.Radiobutton(set_type_window, text="Weight-Based", variable=mode_var, value="weight").pack(pady=2) ttk.Radiobutton(set_type_window, text="Rank-Based", variable=mode_var, value="rank").pack(pady=2) def confirm_set_type(): set_type = set_type_var.get() mode = mode_var.get() if not set_type: messagebox.showerror("Error", "Please select a set type.") return set_frame = ttk.LabelFrame(self.notebook.nametowidget(current_tab), text=f"{set_type} Set ({mode.capitalize()})") set_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) tree = ttk.Treeview(set_frame, columns=("Chain", mode.capitalize()), show="headings") tree.heading("Chain", text="Reverse Moralithic Chain") tree.heading(mode.capitalize(), text=mode.capitalize()) tree.column("Chain", width=600) tree.column(mode.capitalize(), width=100) tree.pack(fill=tk.BOTH, expand=True) tree.bind("", lambda event: self.on_tree_edit(event, tree, set_type, mode, tab_id)) tree.insert("", "end", values=("", "0" if mode == "rank" else "0.0")) ttk.Button(set_frame, text="Add Row", command=lambda: tree.insert("", "end", values=("", "0" if mode == "rank" else "0.0"))).pack(pady=5) self.sets[tab_id][set_type] = {"tree": tree, "mode": mode} set_type_window.destroy() self.canvas.configure(scrollregion=self.canvas.bbox("all")) ttk.Button(set_type_window, text="Confirm", command=confirm_set_type).pack(pady=10) def validate_chain(self, chain, set_type, mode, tab_id, existing_ranks=None): try: pattern = r"^(Non-Interactor|Enforcer|Repeater|Rejecter|Accepter)\s*\[(.*?)\s*\((.*?)\)(?:,\s*(?:Means|Displayment)\s*\((.*?)\s*\((.*?)\)\))?\]-Displayment\s*\[(.*?)\s*\((.*?)\)\]-Values\s*\[(.*?)\]-Statement\s*\[(.*?)\s*\((.*?)\)\]-Source\s*\[(.*?)\s*\((.*?)\)\](?:\s*Footnote: '([^']+)' underwent (\d+) transformation(?:s)?: ([^.\n]*))?$" match = re.match(pattern, chain.strip()) if not match: return False, "Invalid chain format." interactor_type, entity, interaction_tag, extra_field, extra_tag, displayment, displayment_tag, values_str, statement, statement_tag, source, source_tag, footnote_entity, num_trans_str, trans_str = match.groups() means = extra_field if interactor_type == "Enforcer" else "" means_tag = extra_tag if interactor_type == "Enforcer" else "" repeater_displayment = extra_field if interactor_type == "Repeater" else "" repeater_displayment_tag = extra_tag if interactor_type == "Repeater" else "" tab_entity = self.tabs[tab_id]["entity"] if entity != tab_entity: return False, f"Chain entity '{entity}' does not match tab entity '{tab_entity}'." if interactor_type != set_type: return False, f"Chain interactor type '{interactor_type}' does not match set type '{set_type}'." if interaction_tag not in self.valid_interaction_tags: return False, f"Invalid interaction tag '{interaction_tag}'. Must be one of: {', '.join(self.valid_interaction_tags)}." if interactor_type == "Enforcer": if not means_tag: return False, "Means tag is required for Enforcer interaction tags." if means_tag not in self.valid_means_tags: return False, f"Invalid means tag '{means_tag}'. Must be one of: {', '.join(self.valid_means_tags)}." if interactor_type == "Repeater": if not repeater_displayment_tag: return False, "Displayment tag is required for Repeater interaction tags." if repeater_displayment_tag not in self.valid_displayment_tags: return False, f"Invalid repeater displayment tag '{repeater_displayment_tag}'. Must be one of: {', '.join(self.valid_displayment_tags)}." if displayment_tag not in self.valid_displayment_tags: return False, f"Invalid displayment tag '{displayment_tag}'. Must be one of: {', '.join(self.valid_displayment_tags)}." if statement_tag not in self.valid_statement_source_pairs: return False, f"Invalid statement tag '{statement_tag}'. Must be one of: {', '.join(self.valid_statement_source_pairs.keys())}." if source_tag not in self.valid_statement_source_pairs.values(): return False, f"Invalid source tag '{source_tag}'. Must be one of: {', '.join(self.valid_statement_source_pairs.values())}." if self.valid_statement_source_pairs.get(statement_tag) != source_tag: return False, "Incompatible statement and source tags." value_items = re.findall(r"(\d+)\.\s*(.*?)\s*\((.*?)\)", values_str) value_list = [v[1] for v in value_items] value_tags = [v[2] for v in value_items] if len(value_list) < 1: return False, "At least one value is required." if len(value_list) != len(value_tags): return False, f"Mismatch between values ({len(value_list)}) and value tags ({len(value_tags)})." valid_values = self.valid_statement_value_pairs.get(statement_tag, {'values': [], 'required': None})['values'] required_value = self.valid_statement_value_pairs.get(statement_tag, {'values': [], 'required': None})['required'] for v in value_tags: if v not in valid_values: return False, f"Invalid value tag '{v}' for statement tag '{statement_tag}'. Must be one of: {', '.join(valid_values)}." if required_value and required_value not in value_tags: return False, f"Required value tag '{required_value}' for statement tag '{statement_tag}' is missing." statement_words = statement.split() assigned_words = set(value_list) parts_list = [w for w in statement_words if w not in assigned_words] limits_list = [] if not parts_list: return False, "At least one necessary part is required." components = [(v, "Value") for v in value_list] + [(p, "Part") for p in parts_list] + [(l, "Limitation") for l in limits_list] component_positions = [] for comp, comp_type in components: start = statement.find(comp) if start == -1: return False, f"{comp_type} '{comp}' not found in statement." component_positions.append((start, start + len(comp), comp, comp_type)) component_positions.sort() current_pos = 0 for start, end, comp, comp_type in component_positions: if start < current_pos: return False, f"{comp_type} '{comp}' overlaps with previous components at position {start}." current_pos = max(current_pos, end) if footnote_entity: if interactor_type == "Repeater": return False, "Repeaters cannot have transformations." if footnote_entity != entity: return False, "Footnote entity does not match chain entity." if not trans_str: return False, "Footnote transformation path is empty." num_trans = int(num_trans_str) trans_path = [t.strip() for t in trans_str.strip().split("->")] if len(trans_path) != num_trans + 1: return False, f"Footnote transformation count mismatch: expected {num_trans + 1} tags, found {len(trans_path)}." for t in trans_path: if t not in self.valid_interaction_tags: return False, f"Invalid interaction tag in footnote: '{t}'." forward_path = trans_path[::-1] if forward_path[0] != interaction_tag: return False, f"Footnote transformation path does not start with initial interaction tag '{interaction_tag}', found '{forward_path[0]}'." for i in range(num_trans): from_tag = forward_path[i] to_tag = forward_path[i + 1] possible_next = self.forward_conversion_rules.get(from_tag, []) + self.forward_transformation_rules.get(from_tag, []) if to_tag not in possible_next: return False, f"Invalid transformation in footnote: {from_tag}->{to_tag}. Possible next tags: {', '.join(possible_next)}." return True, "" except Exception as e: return False, f"Validation error: {str(e)}" def get_statement_components(self, chain): try: chain_without_footnote = re.sub(r"\s*Footnote:.*$", "", chain) statement_match = re.search(r"Statement\s*\[(.*?)\s*\(\w+\)\]", chain_without_footnote) values_match = re.search(r"Values\s*\[(.*?)\]", chain_without_footnote) if not statement_match or not values_match: return [], [], [] statement = statement_match.group(1) value_items = re.findall(r"\d+\.\s*(.*?)\s*\(\w+\)", values_match.group(1)) value_list = [v for v in value_items] statement_words = statement.split() parts_list = [w for w in statement_words if w not in value_list] limits_list = [] return value_list, parts_list, limits_list except Exception as e: return [], [], [] def apply_statement_colors(self, text_widget, statement, value_list, parts_list, limits_list): try: text_widget.tag_remove("values", "1.0", tk.END) text_widget.tag_remove("parts", "1.0", tk.END) text_widget.tag_remove("limits", "1.0", tk.END) components = [(v, "values") for v in value_list] + [(p, "parts") for p in parts_list] + [(l, "limits") for l in limits_list] component_positions = [] for comp, tag in components: start = statement.find(comp) if start != -1: component_positions.append((start, start + len(comp), comp, tag)) component_positions.sort() for start, end, comp, tag in component_positions: text_widget.tag_add(tag, f"1.0+{start}c", f"1.0+{end}c") return component_positions except Exception as e: return [] def on_tree_edit(self, event, tree, set_type, mode, tab_id): item = tree.identify_row(event.y) column = tree.identify_column(event.x) if not item or not column: return col_index = int(column[1:]) - 1 if col_index not in [0, 1]: return x, y, width, height = tree.bbox(item, column) if col_index == 0: entry = tk.Text(tree, height=2, width=70) else: entry = ttk.Entry(tree) entry.place(x=x, y=y, width=width, height=height) current_value = tree.item(item, "values")[col_index] entry.insert("1.0" if col_index == 0 else 0, current_value) def save_edit(event=None): new_value = entry.get("1.0", tk.END).strip() if col_index == 0 else entry.get() values = list(tree.item(item, "values")) if col_index == 1: if mode == "rank": try: rank = int(new_value) if rank < 1: messagebox.showerror("Error", "Rank must be a positive integer.") entry.destroy() return existing_ranks = [int(tree.item(i, "values")[1]) for i in tree.get_children() if i != item and tree.item(i, "values")[1]] if rank in existing_ranks: messagebox.showerror("Error", f"Rank {rank} is already used in this set.") entry.destroy() return except ValueError: messagebox.showerror("Error", "Rank must be a valid integer.") entry.destroy() return else: try: weight = float(new_value) if weight < 0: messagebox.showerror("Error", "Weight must be a non-negative number.") entry.destroy() return except ValueError: messagebox.showerror("Error", "Weight must be a valid number.") entry.destroy() return values[col_index] = new_value tree.item(item, values=values) if col_index == 0 and new_value: value_list, parts_list, limits_list = self.get_statement_components(new_value) chain_text = tk.Text(tree, height=2, width=70) chain_text.insert("1.0", new_value) chain_text.tag_configure("values", foreground="red") chain_text.tag_configure("parts", foreground="blue") chain_text.tag_configure("limits", foreground="green") statement_match = re.search(r"Statement\s*\[(.*?)\s*\(\w+\)\]", new_value) if statement_match: statement = statement_match.group(1) self.apply_statement_colors(chain_text, statement, value_list, parts_list, limits_list) chain_text.configure(state="disabled") tree.item(item, values=(chain_text.get("1.0", tk.END).strip(), values[1])) entry.destroy() entry.bind("", save_edit) entry.bind("", save_edit) entry.focus_set() def validate_tab(self): current_tab = self.notebook.select() if not current_tab: messagebox.showerror("Error", "No tab selected.") return tab_id = list(self.tabs.keys())[self.notebook.index(current_tab)] if not self.tabs[tab_id]["entity"]: messagebox.showerror("Error", "No entity assigned to this tab.") return set_types = list(self.sets[tab_id].keys()) if not set_types: messagebox.showerror("Error", "No sets in the current tab.") return errors = [] source_tag_errors = [] for set_type in set_types: tree = self.sets[tab_id][set_type]["tree"] mode = self.sets[tab_id][set_type]["mode"] set_errors = [] ranks = [] source_tags = [] for item in tree.get_children(): chain, weight = tree.item(item, "values") if not chain: set_errors.append(f"Row {item}: Empty chain.") continue source_match = re.search(r"Source\s*\[(.*?)\s*\((\w+)\)\]", chain) if source_match: source_tag = source_match.group(2) source_tags.append(source_tag) valid, error = self.validate_chain(chain, set_type, mode, tab_id, ranks) if not valid: set_errors.append(f"Row {item}: {error}") continue try: value = int(weight) if mode == "rank" else float(weight) if mode == "rank": if value in ranks: set_errors.append(f"Row {item}: Duplicate rank {value}.") ranks.append(value) except ValueError: set_errors.append(f"Row {item}: Invalid {mode} '{weight}'.") continue if set_errors: errors.extend([f"{set_type} Set: {e}" for e in set_errors]) if source_tags and len(set(source_tags)) > 1: source_tag_errors.append(f"{set_type} Set: All chains must share the same source tag. Found: {', '.join(set(source_tags))}.") all_errors = errors + source_tag_errors if all_errors: messagebox.showerror("Validation Errors", "\n".join(all_errors)) else: messagebox.showinfo("Success", "All sets in the tab are valid.") def save_set(self): current_tab = self.notebook.select() if not current_tab: messagebox.showerror("Error", "No tab selected.") return tab_id = list(self.tabs.keys())[self.notebook.index(current_tab)] if not self.tabs[tab_id]["entity"]: messagebox.showerror("Error", "No entity assigned to this tab.") return set_types = list(self.sets[tab_id].keys()) if not set_types: messagebox.showerror("Error", "No sets in the current tab.") return save_window = tk.Toplevel(self.root) save_window.title("Select Set to Save") save_window.geometry("300x150") ttk.Label(save_window, text="Select Set Type:").pack(pady=5) set_type_var = tk.StringVar() ttk.Combobox(save_window, textvariable=set_type_var, values=set_types, state="readonly").pack(pady=5) def confirm_save(): set_type = set_type_var.get() if not set_type: messagebox.showerror("Error", "Please select a set type.") return tree = self.sets[tab_id][set_type]["tree"] mode = self.sets[tab_id][set_type]["mode"] set_data = [] errors = [] ranks = [] for item in tree.get_children(): chain, weight = tree.item(item, "values") if not chain: errors.append(f"Row {item}: Empty chain.") continue valid, error = self.validate_chain(chain, set_type, mode, tab_id, ranks) if not valid: errors.append(f"Row {item}: {error}") continue try: value = int(weight) if mode == "rank" else float(weight) if mode == "rank": if value in ranks: errors.append(f"Row {item}: Duplicate rank {value}.") ranks.append(value) set_data.append({"chain": chain, mode: value}) except ValueError: errors.append(f"Row {item}: Invalid {mode} '{weight}'.") continue if errors: messagebox.showerror("Save Errors", "\n".join(errors)) else: try: with open(f"{set_type}_set_{tab_id}.json", "w") as f: json.dump(set_data, f, indent=2) messagebox.showinfo("Success", f"{set_type} Set saved to {set_type}_set_{tab_id}.json") except Exception as e: messagebox.showerror("Error", f"Failed to save set: {str(e)}") save_window.destroy() ttk.Button(save_window, text="Confirm", command=confirm_save).pack(pady=5) if __name__ == "__main__": root = tk.Tk() app = ReverseMoralithicChainSetValidatorMode5(root) root.mainloop()