class MainPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) self.bdm = None self.parent = parent self.entry_list = TreeListCtrl(self, style=TL_MULTIPLE) self.entry_list.AppendColumn("Entry") self.entry_list.Bind(EVT_TREELIST_ITEM_CONTEXT_MENU, self.on_right_click) self.entry_list.Bind(EVT_TREELIST_SELECTION_CHANGED, self.on_select) self.cdo = wx.CustomDataObject("BDMEntry") self.Bind(wx.EVT_MENU, self.on_delete, id=wx.ID_DELETE) self.Bind(wx.EVT_MENU, self.on_copy, id=wx.ID_COPY) self.Bind(wx.EVT_MENU, self.on_paste, id=wx.ID_PASTE) self.Bind(wx.EVT_MENU, self.on_new, id=wx.ID_NEW) accelerator_table = wx.AcceleratorTable([ (wx.ACCEL_CTRL, ord('c'), wx.ID_COPY), (wx.ACCEL_CTRL, ord('v'), wx.ID_PASTE), (wx.ACCEL_NORMAL, wx.WXK_DELETE, wx.ID_DELETE), ]) self.entry_list.SetAcceleratorTable(accelerator_table) pub.subscribe(self.on_select, 'on_select') pub.subscribe(self.convert_for_skill_creator, 'convert_for_skill_creator') # Use some sizers to see layout options sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.entry_list, 1, wx.ALL | wx.EXPAND, 10) # Layout sizers self.SetSizer(sizer) self.SetAutoLayout(1) def build_tree(self): self.entry_list.DeleteAllItems() root = self.entry_list.GetRootItem() for entry in sorted(self.bdm.entries, key=lambda e: e.id): self.entry_list.AppendItem(root, f'{entry.id}: Entry', data=entry) def get_current_entry_ids(self): entry_ids = [] item = self.entry_list.GetFirstItem() while item.IsOk(): data = self.entry_list.GetItemData(item) entry_ids.append(data.id) item = self.entry_list.GetNextItem(item) return entry_ids def get_previous_entry(self, entry_id): item = self.entry_list.GetFirstItem() prev = TLI_FIRST while item.IsOk(): data = self.entry_list.GetItemData(item) if data.id > entry_id: break prev, item = item, self.entry_list.GetNextItem(item) return prev def on_right_click(self, _): selected = self.entry_list.GetSelections() if not selected: return menu = wx.Menu() menu.Append(wx.ID_NEW) menu.Append(wx.ID_DELETE) menu.Append(wx.ID_COPY) paste = menu.Append(wx.ID_PASTE) add = menu.Append(wx.ID_ADD, '&Add Copied Entry') success = False # Check Clipboard if wx.TheClipboard.Open(): success = wx.TheClipboard.IsSupported(wx.DataFormat("BDMEntry")) wx.TheClipboard.Close() add.Enable(success) paste.Enable(success) self.PopupMenu(menu) menu.Destroy() def on_select(self, _): selected = self.entry_list.GetSelections() if len(selected) != 1: pub.sendMessage('disable') return pub.sendMessage('load_entry', entry=self.entry_list.GetItemData(selected[0])) def add_entry(self, entry): root = self.entry_list.GetRootItem() prev = self.get_previous_entry(entry.id) item = self.entry_list.InsertItem(root, prev, f'{entry.id}: Entry', data=entry) self.entry_list.Select(item) self.on_select(None) def on_new(self, _): # Ask for ID if self.bdm is None: return with NewEntryDialog(self, self.get_current_entry_ids()) as dlg: if dlg.ShowModal() != wx.ID_OK: return entry_id = dlg.GetValue() self.entry_list.UnselectAll() # Add it entry = Entry(entry_id=entry_id) self.add_entry(entry) pub.sendMessage('set_status_bar', text='Added new entry') def on_add(self, _): if self.bdm is None: return cdo = wx.CustomDataObject("BDMEntry") success = False if wx.TheClipboard.Open(): success = wx.TheClipboard.GetData(cdo) wx.TheClipboard.Close() if not success: with wx.MessageDialog(self, 'Unable to get copied data') as dlg: dlg.ShowModal() return paste_data = pickle.loads(cdo.GetData()) # Get new Id's with NewEntryDialog(self, self.get_current_entry_ids()) as dlg: if dlg.ShowModal() != wx.ID_OK: return entry_id = dlg.GetValue() current_entry_ids = self.get_current_entry_ids() self.entry_list.UnselectAll() # Paste for paste in paste_data: while entry_id in current_entry_ids: entry_id += 1 entry = Entry(entry_id=entry_id) entry.paste(paste) self.add_entry(entry) entry_id += 1 self.on_select(None) pub.sendMessage(f'Pasted {len(paste_data)} new entry(s)') def on_delete(self, _): selected = self.entry_list.GetSelections() if not selected: return for item in selected: self.entry_list.DeleteItem(item) pub.sendMessage('disable') pub.sendMessage('set_status_bar', text=f'Deleted {len(selected)} entries') def on_copy(self, _): selected = self.entry_list.GetSelections() self.cdo = wx.CustomDataObject("BDMEntry") self.cdo.SetData( pickle.dumps( [self.entry_list.GetItemData(item) for item in selected])) if wx.TheClipboard.Open(): wx.TheClipboard.SetData(self.cdo) wx.TheClipboard.Flush() wx.TheClipboard.Close() pub.sendMessage('set_status_bar', text=f'Copied {len(selected)} entries') def on_paste(self, _): selected = self.entry_list.GetSelections() if not selected: return success = False cdo = wx.CustomDataObject("BDMEntry") if wx.TheClipboard.Open(): success = wx.TheClipboard.GetData(cdo) wx.TheClipboard.Close() if success: paste_data = pickle.loads(cdo.GetData()) paste_length = len(paste_data) selected_length = len(selected) if selected_length > paste_length: for item in selected[paste_length:]: self.entry_list.Unselect(item) selected = selected[:paste_length] item = selected[-1] self.entry_list.Select(item) for n in range(paste_length - selected_length): item = self.entry_list.GetNextItem(item) if not item.IsOk(): with wx.MessageDialog( self, f'Not enough entries to paste over. Expected {paste_length}' ) as dlg: dlg.ShowModal() return self.entry_list.Select(item) selected.append(item) if len(selected) > 1: msg = '\n'.join([ f' * {self.entry_list.GetItemData(item).id}: Entry' for item in selected ]) with MultiMessageDialog( self, 'Are you sure you want to replace the following entries?', 'Warning', msg, wx.YES | wx.NO) as dlg: if dlg.ShowModal() != wx.ID_YES: return for n, paste in enumerate(paste_data): data = self.entry_list.GetItemData(selected[n]) data.paste(paste) self.on_select(None) pub.sendMessage('set_status_bar', text=f'Pasted {len(paste_data)} entry(s)') def convert_for_skill_creator(self): if not self.bdm: with wx.MessageDialog(self, "No BDM loaded!", "Error") as dlg: dlg.ShowModal() return # Get choices choices = set() item = self.entry_list.GetFirstItem() effect_ids = [ 'effect_1_skill_id', 'effect_2_skill_id', 'effect_3_skill_id' ] while item.IsOk(): data = self.entry_list.GetItemData(item) item = self.entry_list.GetNextItem(item) for sub_entry in data.sub_entries: for effect_id in effect_ids: try: if sub_entry[effect_id] != 0 and sub_entry[ effect_id] != 0xFFFF and sub_entry[ effect_id] != 0xBACA: choices.update([str(sub_entry[effect_id])]) except Exception as e: pass if not choices: with wx.MessageDialog(self, "Cannot find any Skill IDs to convert", "Error") as dlg: dlg.ShowModal() return # Show Dialog with ConvertDialog(self, list(choices)) as dlg: if dlg.ShowModal() != wx.ID_OK: return skill_id = dlg.GetValue() # Do Conversion item = self.entry_list.GetFirstItem() changed = 0 while item.IsOk(): data = self.entry_list.GetItemData(item) item = self.entry_list.GetNextItem(item) for sub_entry in data.sub_entries: for effect_id in effect_ids: if sub_entry[effect_id] == skill_id: try: sub_entry[effect_id] = 0xBACA changed += 1 except Exception: pass self.on_select(None) pub.sendMessage('set_status_bar', text=f'Changed {changed} skill ids to 0xBACA') def reindex(self): selected = self.entry_list.GetSelections() if len(selected) != 1: return item = selected[0] entry = self.entry_list.GetItemData(item) self.entry_list.DeleteItem(item) self.add_entry(entry)
class SidePanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) self.code = '' self.bac = None self.bdm = None self.ean = None self.cam_ean = None self.parent = parent self.dirname = '' # Name self.name = wx.StaticText(self, -1, '(No file loaded)') self.font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD) self.name.SetFont(self.font) # Buttons self.open = wx.Button(self, wx.ID_OPEN, "Load") self.copy = wx.Button(self, wx.ID_COPY, "Copy") self.copy.Disable() # Entry List self.entry_list = TreeListCtrl(self, style=TL_MULTIPLE) self.entry_list.AppendColumn("BAC Entry") self.entry_list.AppendColumn("Copied", width=64) self.entry_list.Bind(EVT_TREELIST_ITEM_CONTEXT_MENU, self.on_right_click) # self.cdo = wx.CustomDataObject("BDMEntry") self.Bind(wx.EVT_BUTTON, self.on_open, id=wx.ID_OPEN) self.Bind(wx.EVT_BUTTON, self.on_copy, id=wx.ID_COPY) self.Bind(wx.EVT_MENU, self.on_copy, id=wx.ID_COPY) accelerator_table = wx.AcceleratorTable([ (wx.ACCEL_CTRL, ord('c'), wx.ID_COPY), ]) self.entry_list.SetAcceleratorTable(accelerator_table) self.SetDropTarget(FileDropTarget(self, "load_side_moveset")) # Button Sizer button_sizer = wx.BoxSizer(wx.HORIZONTAL) button_sizer.Add(self.open) button_sizer.AddSpacer(5) button_sizer.Add(self.copy) # Use some sizers to see layout options sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.name, 0, wx.CENTER) sizer.Add(button_sizer) sizer.Add(self.entry_list, 1, wx.ALL | wx.EXPAND, 10) # Layout sizers self.SetSizer(sizer) self.SetAutoLayout(1) def on_open(self, _): pub.sendMessage('open_side_moveset') def build_tree(self): self.entry_list.DeleteAllItems() root = self.entry_list.GetRootItem() for i, entry in enumerate(self.bac.entries): if not entry.sub_entries: continue self.entry_list.AppendItem( root, f'{entry.index}: {KNOWN_ENTRIES.get(entry.index, "Unknown")}', data=entry) self.copy.Enable() self.parent.copied = None def on_right_click(self, _): selected = self.entry_list.GetSelections() if not selected: return menu = wx.Menu() menu.Append(wx.ID_COPY) self.PopupMenu(menu) menu.Destroy() def on_copy(self, _): selected = self.entry_list.GetSelections() if not selected: return # Deselect all item = self.entry_list.GetFirstItem() while item.IsOk(): self.entry_list.SetItemText(item, 1, '') item = self.entry_list.GetNextItem(item) # Check and add to copied copied = [] for item in selected: copied.append(self.entry_list.GetItemData(item)) self.entry_list.SetItemText(item, 1, CHECK) self.parent.copied = pickle.dumps(copied) pub.sendMessage('set_status_bar', text=f'Copied {len(selected)} entries')
class MainPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) self.code = '' self.bac = None self.bdm = None self.ean = None self.cam_ean = None self.dirname = '' self.parent = parent self.links = defaultdict( lambda: defaultdict(lambda: defaultdict(dict))) # Name self.name = wx.StaticText(self, -1, '(No file loaded)') self.font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD) self.name.SetFont(self.font) # Buttons self.open = wx.Button(self, wx.ID_OPEN, "Load") self.save = wx.Button(self, wx.ID_SAVE, "Save") self.save.Disable() self.paste = wx.Button(self, wx.ID_PASTE, "Paste") self.paste.Disable() self.add = wx.Button(self, wx.ID_ADD, "Add Copy") self.add.Disable() # Entry List self.entry_list = TreeListCtrl(self, style=TL_MULTIPLE) self.entry_list.AppendColumn("BAC Entry") self.entry_list.Bind(EVT_TREELIST_ITEM_CONTEXT_MENU, self.on_right_click) # Bind self.Bind(wx.EVT_BUTTON, self.on_open, id=wx.ID_OPEN) self.Bind(wx.EVT_BUTTON, self.on_save, id=wx.ID_SAVE) self.Bind(wx.EVT_BUTTON, self.on_paste, id=wx.ID_PASTE) self.Bind(wx.EVT_BUTTON, self.on_add, id=wx.ID_ADD) self.Bind(wx.EVT_MENU, self.on_paste, id=wx.ID_PASTE) accelerator_table = wx.AcceleratorTable([ (wx.ACCEL_CTRL, ord('v'), wx.ID_PASTE), ]) self.entry_list.SetAcceleratorTable(accelerator_table) self.SetDropTarget(FileDropTarget(self, "load_main_moveset")) # Button Sizer button_sizer = wx.BoxSizer(wx.HORIZONTAL) button_sizer.Add(self.open) button_sizer.AddSpacer(5) button_sizer.Add(self.save) button_sizer.AddSpacer(5) button_sizer.Add(self.paste) button_sizer.AddSpacer(5) button_sizer.Add(self.add) # Use some sizers to see layout options sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.name, 0, wx.CENTER) sizer.Add(button_sizer) sizer.Add(self.entry_list, 1, wx.ALL | wx.EXPAND, 10) # Layout sizers self.SetSizer(sizer) self.SetAutoLayout(1) def on_open(self, _): pub.sendMessage('open_main_moveset') def on_save(self, _): pub.sendMessage('save_moveset') def build_tree(self): self.entry_list.DeleteAllItems() root = self.entry_list.GetRootItem() for i, entry in enumerate(self.bac.entries): if not entry.sub_entries: continue self.entry_list.AppendItem( root, f'{entry.index}: {KNOWN_ENTRIES.get(entry.index, "Unknown")}', data=entry) self.save.Enable() self.paste.Enable() self.add.Enable() def on_right_click(self, _): selected = self.entry_list.GetSelections() if not selected: return menu = wx.Menu() paste = menu.Append(wx.ID_PASTE) paste.Enable(self.parent.copied is not None) self.PopupMenu(menu) menu.Destroy() def find_next_available_index(self, item_type): if item_type == Animation: return len(self.ean.animations) elif item_type == Hitbox: return max([entry.id for entry in self.bdm.entries]) + 1 elif item_type == Camera: return len(self.cam_ean.animations) else: raise (TypeError(f'Unsupported type: {item_type.__name__}')) def find_conflict(self, item_type, entry_pair, depend_value, selected_data, value): if value in list( self.links[item_type][entry_pair][depend_value].values()): return True for entry in self.bac.entries: # Skip this entry if its already in the selected data if entry in selected_data: continue for sub_entry in entry.sub_entries: # Skip if this is not the type we want if item_type != ITEM_TYPES[sub_entry.type]: continue for item in sub_entry.items: if item[entry_pair[1]] == depend_value and item[ entry_pair[0]] == value: return True return False def create_new_index(self, item_type): new_value = self.find_next_available_index(item_type) if item_type == Animation: animation = EanAnimation(self.ean) self.ean.animations.append(animation) elif item_type == Hitbox: self.bdm.entries.append(BdmEntry(entry_id=new_value)) elif item_type == Camera: self.cam_ean.animations.append(EanAnimation(self.cam_ean)) else: raise (TypeError(f'Unsupported type: {item_type.__name__}')) return new_value def file_not_found_dialog(self, filename): with wx.MessageDialog( self, f'{filename} was not opened. Please add it and reload the moveset', 'Error') as dlg: dlg.ShowModal() def invalid_index_dialog(self, filename, index): with wx.MessageDialog(self, f'{filename} does not contain index {index}', 'Error') as dlg: dlg.ShowModal() def copy_index(self, item_type, old_value, new_value): new_code = self.code old_code = self.parent.side_panel.code if item_type == Animation: try: animation = self.ean.animations[new_value] except IndexError: self.invalid_index_dialog(f'{new_code}.ean', new_value) return False try: animation.paste( self.parent.side_panel.ean.animations[old_value], keep_name=True) except IndexError: self.invalid_index_dialog(f'{old_code}.ean', old_value) return False elif item_type == Hitbox: if not self.bdm: self.file_not_found_dialog(f'{new_code}_PLAYER.bdm') return False if not self.parent.side_panel.bdm: self.file_not_found_dialog(f'{old_code}_PLAYER.bdm') return False try: entry = [ entry for entry in self.bdm.entries if entry.id == new_value ][0] except IndexError: self.invalid_index_dialog(f'{new_code}_PLAYER.bdm', new_value) return False try: old_entry = [ entry for entry in self.parent.side_panel.bdm.entries if entry.id == old_value ][0] except IndexError: self.invalid_index_dialog(f'{old_code}_PLAYER.bdm', old_value) return False entry.paste(old_entry) elif item_type == Camera: if not self.cam_ean: self.file_not_found_dialog(f'{new_code}.cam.ean') return False if not self.parent.side_panel.cam_ean: self.file_not_found_dialog(f'{old_code}.cam.ean') return False try: camera = self.cam_ean.animations[new_value] except IndexError: self.invalid_index_dialog(f'{new_code}.cam.ean', new_value) return False try: camera.paste( self.parent.side_panel.cam_ean.animations[old_value], keep_name=True) except IndexError: self.invalid_index_dialog(f'{old_code}.cam.ean', old_value) return False else: raise (TypeError(f'Unsupported type: {item_type.__name__}')) return True def changed_value_message(self, entry_index, changed_values, item_type, old_value, new_value, new=True): old_string = str(old_value) new_string = str(new_value) if item_type == Animation: old_string += f' ({self.parent.side_panel.ean.animations[old_value].name})' new_string += f' ({"*new*" if new else self.ean.animations[new_value].name})' changed_values[item_type].append( (f'[{entry_index}]', new_string, old_string)) def get_changed_values(self, changed_values, item_type, entry_pair, depend_value, entry_values, selected_val, selected_data): entry_index = selected_val[0] selected_val = selected_val[1] entry_name = KNOWN_ENTRIES.get(entry_index, 'Unknown') n = 0 for old_value in entry_values: # Continue if we already have a link old_animation_name = self.parent.side_panel.ean.animations[old_value].name \ if item_type == Animation and old_value < len(self.parent.side_panel.ean.animations) else '' if item_type == Animation and any( word in old_animation_name for word in BLACKLISTED_WORDS) \ and not old_animation_name.endswith(entry_name): continue if old_value in self.links[item_type][entry_pair][depend_value]: new_value = self.links[item_type][entry_pair][depend_value][ old_value] # if (old_value, new_value) in list(zip(*changed_values[item_type])): self.changed_value_message(entry_index, changed_values, item_type, old_value, new_value, False) continue # If the current n is bigger then the selected values or selected values doesn't exist if item_type in selected_val and entry_pair in selected_val[ item_type]: if n >= len(selected_val[item_type][entry_pair][depend_value]): new_value = self.create_new_index(item_type) self.changed_value_message(entry_index, changed_values, item_type, old_value, new_value) else: while n < len( selected_val[item_type][entry_pair][depend_value]): new_value = list(selected_val[item_type][entry_pair] [depend_value])[n] new_animation_name = self.ean.animations[new_value].name \ if item_type == Animation and new_value < len(self.ean.animations) else '' if item_type != Animation or not any( word in new_animation_name for word in BLACKLISTED_WORDS) \ or new_animation_name.endswith(entry_name): if not new_animation_name.endswith( entry_name) and self.find_conflict( item_type, entry_pair, depend_value, selected_data, new_value): new_value = self.create_new_index(item_type) self.changed_value_message( entry_index, changed_values, item_type, old_value, new_value) else: self.changed_value_message( entry_index, changed_values, item_type, old_value, new_value, False) break n += 1 else: new_value = self.create_new_index(item_type) self.changed_value_message(entry_index, changed_values, item_type, old_value, new_value) else: new_value = self.create_new_index(item_type) self.changed_value_message(entry_index, changed_values, item_type, old_value, new_value) # Copy EAN/BDM entries if not self.copy_index(item_type, old_value, new_value): return False self.links[item_type][entry_pair][depend_value][ old_value] = new_value n += 1 return True def on_paste(self, _): if not self.parent.copied: with wx.MessageDialog( self, f'No entries are copied from the right panel to Paste' ) as dlg: dlg.ShowModal() return selected = self.entry_list.GetSelections() if not selected: with wx.MessageDialog( self, f'No entries are selected to Paste onto') as dlg: dlg.ShowModal() return copied = pickle.loads(self.parent.copied) # Cut length of selected to match copied copy_length = len(copied) selected_length = len(selected) if selected_length > copy_length: for item in selected[copy_length:]: self.entry_list.Unselect(item) selected = selected[:copy_length] item = selected[-1] self.entry_list.Select(item) # Increase length to match selected for n in range(copy_length - selected_length): item = self.entry_list.GetNextItem(item) if not item.IsOk(): with wx.MessageDialog( self, f'Not enough entries to paste over. Expected {copy_length}' ) as dlg: dlg.ShowModal() return self.entry_list.Select(item) selected.append(item) selected_data = [ self.entry_list.GetItemData(item) for item in selected ] # Warn about changing multiple entries if len(copied) > 1: msg = '' for n, copied_data in enumerate(copied): msg += f' * {selected_data[n].index} -> {copied_data.index}\n' with MultiMessageDialog( self, 'Are you sure you want to replace the following entries?', 'Warning', msg, wx.YES | wx.NO) as dlg: if dlg.ShowModal() != wx.ID_YES: return # Paste entries selected_values = [] copied_values = [] changed_values = defaultdict(list) for n, copied_data in enumerate(copied): selected_values.append( (selected_data[n].index, selected_data[n].get_static_values())) copied_values.append(copied_data.get_static_values()) for selected_val, copied_val in zip(selected_values, copied_values): # Example: # Item type: Animation # entry: Index # dependency: Type # depend_value: 5 # entry_values: {1, 2, 3} for item_type, v1 in copied_val.items(): if item_type not in [Animation, Hitbox, Camera]: continue for entry_pair, v2 in v1.items(): for depend_value, entry_values in v2.items(): # Skip if dependency isn't a character if item_type.dependencies[entry_pair][ depend_value] != 'Character': continue if not self.get_changed_values( changed_values, item_type, entry_pair, depend_value, entry_values, selected_val, selected_data): return # Finally copy BAC Entries for n, copied_data in enumerate(copied): entry = self.entry_list.GetItemData(selected[n]) entry.paste(copied_data, self.links) # Display message msg = f'Pasted {len(copied)} entry(s)' pub.sendMessage('set_status_bar', text=msg) with ChangedDialog(self, changed_values) as dlg: dlg.ShowModal() # changed_msg = '' # for item_type, values in changed_values.items(): # if item_type == Animation: # changed_msg += f'{self.code}.ean -> {self.parent.side_panel.code}.ean\n' # elif item_type == Hitbox: # changed_msg += f'{self.code}_PLAYER.bdm -> {self.parent.side_panel.code}_PLAYER.bdm\n' # elif item_type == Camera: # changed_msg += f'{self.code}.cam.ean -> {self.parent.side_panel.code}.cam.ean\n' # else: # raise(TypeError(f'Unsupported type: {item_type.__name__}')) # changed_msg += ''.join(sorted(list(zip(*values))[1])) # with MultiMessageDialog( # self, 'The following entries in the listed files have been changed:', msg, changed_msg, wx.OK) as dlg: # dlg.ShowModal() def on_add(self, _): if not self.parent.copied: with wx.MessageDialog( self, f'No entries are copied from the right panel to Add' ) as dlg: dlg.ShowModal() return copied = pickle.loads(self.parent.copied) selected_data = [item for item in copied] # Paste entries selected_values = [] copied_values = [] changed_values = defaultdict(list) for n, copied_data in enumerate(copied): selected_values.append( (selected_data[n].index, selected_data[n].get_static_values())) copied_values.append(copied_data.get_static_values()) # same code as in on_paste(), but it compares to the added entry (i really have no idea why this works..) for selected_val, copied_val in zip(selected_values, copied_values): for item_type, v1 in copied_val.items(): if item_type not in [Animation, Hitbox, Camera]: continue for entry_pair, v2 in v1.items(): for depend_value, entry_values in v2.items(): # Skip if dependency isn't a character if item_type.dependencies[entry_pair][ depend_value] != 'Character': continue if not self.get_changed_values( changed_values, item_type, entry_pair, depend_value, entry_values, selected_val, selected_data): return # Add BAC Entry for n, copied_data in enumerate(copied): # add new entries to the end so we don't override important CMN entries index = len(self.bac.entries) new_entry = Entry(self.bac, index) root = self.entry_list.GetRootItem() new_entry.paste(copied_data, self.links) self.bac.entries.append(new_entry) self.entry_list.AppendItem( root, f'{new_entry.index}: {KNOWN_ENTRIES.get(new_entry.index, "Unknown")}', data=new_entry) # Display message msg = f'Added {len(copied)} entry(s)' pub.sendMessage('set_status_bar', text=msg) with ChangedDialog(self, changed_values) as dlg: dlg.ShowModal()
class ParameterView(wx.Panel): title = 'Parameters' default_size = (640, 500) def __init__(self, *args, **kw): wx.Panel.__init__(self, *args, **kw) #sizers vbox = wx.BoxSizer(wx.VERTICAL) text_hbox = wx.BoxSizer(wx.HORIZONTAL) self.tree = TreeListCtrl( self, -1, style=wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS | wx.TR_TWIST_BUTTONS | wx.TR_ROW_LINES #| wx.TR_COLUMN_LINES | wx.TR_NO_LINES | wx.TR_FULL_ROW_HIGHLIGHT) # CRUFT: wx3 AddColumn => wx4 AppendColumn if phoenix: self.tree.AddColumn = self.tree.AppendColumn self.tree.GetItemPyData = self.tree.GetItemData self.tree.SetItemPyData = self.tree.SetItemData self.tree.GetNext = self.tree.GetNextItem self.tree.ExpandAll = self.tree.Expand # Create columns. self.tree.AddColumn("Model") self.tree.AddColumn("Parameter") self.tree.AddColumn("Value") self.tree.AddColumn("Minimum") self.tree.AddColumn("Maximum") self.tree.AddColumn("Fit?") # Align the textctrl box with treelistctrl. self.tree.SetColumnWidth(0, 180) self.tree.SetColumnWidth(1, 150) self.tree.SetColumnWidth(2, 73) self.tree.SetColumnWidth(3, 73) self.tree.SetColumnWidth(4, 73) self.tree.SetColumnWidth(5, 40) # Determine which colunms are editable. if not phoenix: # CRUFT: wx4 needs to witch to DataViewCtrl self.tree.SetMainColumn(0) # the one with the tree in it... self.tree.SetColumnEditable(0, False) self.tree.SetColumnEditable(1, False) self.tree.SetColumnEditable(2, True) self.tree.SetColumnEditable(3, True) self.tree.SetColumnEditable(4, True) self.tree.SetColumnEditable(5, False) self.tree.GetMainWindow().Bind(wx.EVT_RIGHT_UP, self.OnRightUp) self.tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnEndEdit) ''' self.tree.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP,self.OnTreeTooltip) wx.EVT_MOTION(self.tree, self.OnMouseMotion) ''' vbox.Add(self.tree, 1, wx.EXPAND) self.SetSizer(vbox) self.SetAutoLayout(True) self._need_update_parameters = self._need_update_model = False self.Bind(wx.EVT_SHOW, self.OnShow) # ============= Signal bindings ========================= ''' def OnTreeTooltip(self, event): itemtext = self.tree.GetItemText(event.GetItem()) event.SetToolTip("This is a ToolTip for %s!" % itemtext) event.Skip() def OnMouseMotion(self, event): pos = event.GetPosition() item, flags, col = self.tree.HitTest(pos) if wx.TREE_HITTEST_ONITEMLABEL: self.tree.SetToolTipString("tool tip") else: self.tree.SetToolTipString("") event.Skip() ''' def OnShow(self, event): if not event.Show: return #print "showing parameter" if self._need_update_model: #print "-model update" self.update_model(self.model) elif self._need_update_parameters: #print "-parameter update" self.update_parameters(self.model) event.Skip() # ============ Operations on the model =============== def get_state(self): return self.model def set_state(self, state): self.set_model(state) def set_model(self, model): self.model = model self.update_model(model) def update_model(self, model): if self.model != model: return if not IS_MAC and not self.IsShown(): self._need_update_model = True else: self._need_update_model = self._need_update_parameters = False self._update_model() def update_parameters(self, model): if self.model != model: return if not IS_MAC and not self.IsShown(): self._need_update_parameters = True else: self._need_update_parameters = False self._update_tree_nodes() def _update_model(self): # Delete the previous tree (if any). self.tree.DeleteAllItems() if self.model is None: return parameters = self.model.model_parameters() # Add a root node. if phoenix: # CRUFT: wx 3/4 self.root = self.tree.GetRootItem() else: self.root = self.tree.AddRoot("Model") # Add nodes from our data set . self._add_tree_nodes(self.root, parameters) self._update_tree_nodes() self.tree.ExpandAll(self.root) def _add_tree_nodes(self, branch, nodes): if isinstance(nodes, dict) and nodes != {}: for k in sorted(nodes.keys()): child = self.tree.AppendItem(branch, k) self._add_tree_nodes(child, nodes[k]) elif ((isinstance(nodes, tuple) and nodes != ()) or (isinstance(nodes, list) and nodes != [])): for i, v in enumerate(nodes): child = self.tree.AppendItem(branch, '[%d]' % i) self._add_tree_nodes(child, v) elif isinstance(nodes, BaseParameter): self.tree.SetItemPyData(branch, nodes) def _update_tree_nodes(self): node = self.tree.GetRootItem() while node.IsOk(): self._set_leaf(node) node = self.tree.GetNext(node) def _set_leaf(self, branch): par = self.tree.GetItemPyData(branch) if par is None: return if par.fittable: if par.fixed: fitting_parameter = 'No' low, high = '', '' else: fitting_parameter = 'Yes' low, high = (str(v) for v in par.bounds.limits) else: fitting_parameter = '' low, high = '', '' if phoenix: # CRUFT: wx 3/4 self.tree.SetItemText(branch, 1, str(par.name)) self.tree.SetItemText(branch, 2, str(nice(par.value))) self.tree.SetItemText(branch, 3, low) self.tree.SetItemText(branch, 4, high) self.tree.SetItemText(branch, 5, fitting_parameter) else: self.tree.SetItemText(branch, str(par.name), 1) self.tree.SetItemText(branch, str(nice(par.value)), 2) self.tree.SetItemText(branch, low, 3) self.tree.SetItemText(branch, high, 4) self.tree.SetItemText(branch, fitting_parameter, 5) def OnRightUp(self, evt): pos = evt.GetPosition() branch, flags, column = self.tree.HitTest(pos) if column == 5: par = self.tree.GetItemPyData(branch) if par is None: return if par.fittable: fitting_parameter = self.tree.GetItemText(branch, column) if fitting_parameter == 'No': par.fixed = False fitting_parameter = 'Yes' low, high = (str(v) for v in par.bounds.limits) elif fitting_parameter == 'Yes': par.fixed = True fitting_parameter = 'No' low, high = '', '' self.tree.SetItemText(branch, low, 3) self.tree.SetItemText(branch, high, 4) self.tree.SetItemText(branch, fitting_parameter, 5) signal.update_model(model=self.model, dirty=False) def OnEndEdit(self, evt): item = self.tree.GetSelection() self.node_object = self.tree.GetItemPyData(evt.GetItem()) # TODO: Not an efficient way of updating values of Parameters # but it is hard to find out which column changed during edit # operation. This may be fixed in the future. wx.CallAfter(self.get_new_name, item, 1) wx.CallAfter(self.get_new_value, item, 2) wx.CallAfter(self.get_new_min, item, 3) wx.CallAfter(self.get_new_max, item, 4) def get_new_value(self, item, column): new_value = self.tree.GetItemText(item, column) # Send update message to other tabs/panels only if parameter value # is updated . if new_value != str(self.node_object.value): self.node_object.clip_set(float(new_value)) signal.update_parameters(model=self.model) def get_new_name(self, item, column): new_name = self.tree.GetItemText(item, column) # Send update message to other tabs/panels only if parameter name # is updated. if new_name != str(self.node_object.name): self.node_object.name = new_name signal.update_model(model=self.model, dirty=False) def get_new_min(self, item, column): low = self.tree.GetItemText(item, column) if low == '': return low = float(low) high = self.node_object.bounds.limits[1] # Send update message to other tabs/panels only if parameter min range # value is updated. if low != self.node_object.bounds.limits[0]: self.node_object.range(low, high) signal.update_model(model=self.model, dirty=False) def get_new_max(self, item, column): high = self.tree.GetItemText(item, column) if high == '': return low = self.node_object.bounds.limits[0] high = float(high) # Send update message to other tabs/panels only if parameter max range # value is updated. if high != self.node_object.bounds.limits[1]: self.node_object.range(low, high) signal.update_model(model=self.model, dirty=False)