Ejemplo n.º 1
0
class AddMissingBonesDialog(wx.Dialog):
    def __init__(self, parent, missing_bones, *args, **kw):
        super().__init__(parent, *args, **kw)

        self.SetTitle("Add Missing Bones")
        self.missing_bones = missing_bones

        self.bone_list = TreeListCtrl(self,
                                      size=(-1, 250),
                                      style=TL_MULTIPLE | TL_CHECKBOX)
        self.bone_list.AppendColumn("Bone")
        root = self.bone_list.GetRootItem()
        for bone in missing_bones:
            item = self.bone_list.AppendItem(root, bone.name, data=bone)
            self.bone_list.CheckItem(item)

        add_button = wx.Button(self, wx.ID_OK, "Add")
        add_button.SetDefault()
        no_add_button = wx.Button(self, wx.ID_CANCEL, "Don't Add")

        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        button_sizer.Add(add_button, 0, wx.LEFT | wx.RIGHT, 2)
        button_sizer.Add(no_add_button, 0, wx.LEFT | wx.RIGHT, 5)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(
            wx.StaticText(
                self, -1, 'The current EAN is missing the following bones.\n'
                'Do you want to add them?'), 0, wx.ALL, 10)
        sizer.Add(self.bone_list, 1, wx.ALL | wx.EXPAND, 10)
        sizer.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 10)
        sizer.Add(button_sizer, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)

        self.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()

    def GetValues(self):
        missing_bones = []
        item = self.bone_list.GetFirstItem()
        while item.IsOk():
            if self.bone_list.GetCheckedState(item) == wx.CHK_CHECKED:
                missing_bones.append(self.bone_list.GetItemData(item))
            item = self.bone_list.GetNextItem(item)
        return missing_bones
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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()
Ejemplo n.º 4
0
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')
Ejemplo n.º 5
0
class MainFrame(wx.Frame):
    """
    This is the main frame of the program, into which the tree is put
    
    Functions that start with "On" are event handlers, which you can see because event is the second argument to the function
    """
    def __init__(self, parent, root_path = os.path.expanduser("~"), *args, **kwargs):
        size = (800, 800) #Default size
        
        # If preferences.json exists, load it and use it to specify the window size
        pref_path = os.path.join(app_root_path, 'preferences.json')
        if os.path.exists(pref_path):
            with open(pref_path,'r') as fp:
                options = json.load(fp)
                if 'size' in options:
                    size = options['size']
        wx.Frame.__init__(self, parent, title='Winder', *args, size = size, **kwargs)        
        
        self.make_menu_bar()
        vsizer = wx.BoxSizer(wx.VERTICAL)
        
        self.tree = TreeListCtrl(self, -1, style = wx.dataview.TL_MULTIPLE | wx.dataview.TL_CHECKBOX )

        self.isz = (16,16)
        il = wx.ImageList(*self.isz)
        self.fldridx     = il.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER,      wx.ART_OTHER, self.isz))
        self.fldropenidx = il.Add(wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN,   wx.ART_OTHER, self.isz))
        self.fileidx     = il.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, self.isz))

        self.tree.SetImageList(il)
        self.il = il
            
        self.root_path = root_path
        self.build_tree()
        
        # Set the acceleratators to manually intercept some keystrokes
        self.set_accelerators()
        vsizer.Add(self.tree, 1, wx.EXPAND)
        self.SetSizer(vsizer)
        
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        
    def build_tree(self):
        
        self.tree.ClearColumns()
        self.tree.DeleteAllItems()
        
        # create some columns
        self.tree.AppendColumn("Main column", align = wx.ALIGN_RIGHT, flags = wx.COL_SORTABLE|wx.COL_RESIZABLE)
        self.tree.AppendColumn("Size (B)", width = wx.COL_WIDTH_AUTOSIZE, align = wx.ALIGN_RIGHT, flags = wx.COL_SORTABLE|wx.COL_RESIZABLE)
        self.tree.AppendColumn("Day", width = wx.COL_WIDTH_AUTOSIZE, align = wx.ALIGN_RIGHT, flags = wx.COL_SORTABLE|wx.COL_RESIZABLE)
        self.tree.AppendColumn("Time", width = wx.COL_WIDTH_AUTOSIZE, align = wx.ALIGN_RIGHT, flags = wx.COL_SORTABLE|wx.COL_RESIZABLE)
        self.tree.AppendColumn("", width = 20, align = wx.ALIGN_RIGHT, flags = wx.COL_SORTABLE|wx.COL_RESIZABLE)
        
        self.root = self.tree.AppendItem(self.tree.GetRootItem(), self.root_path, self.fldridx, self.fldropenidx)
        
        self.tree.GetView().Bind(wx.EVT_KEY_DOWN, self.OnTreeKeyPress)
        self.tree.GetView().Bind(wx.EVT_CHAR, self.OnTreeChar)
        self.Bind(wx.dataview.EVT_TREELIST_ITEM_ACTIVATED, self.OnTreeDoubleClick, self.tree)
        self.tree.GetDataView().Bind(wx.dataview.EVT_DATAVIEW_COLUMN_HEADER_CLICK, self.OnHeaderClick)
        self.Bind(wx.dataview.EVT_TREELIST_ITEM_EXPANDING, self.OnExpandTreeListLeaf, self.tree)
                
        self.populate_tree(self.root, self.root_path)
        
        self.tree.SetItemComparator(comparator)
        self.tree.Expand(self.root)
        
        for col in [col_size, col_day, col_time]:
            self.tree.SetColumnWidth(col, self.tree.GetColumnWidth(col) + 5)
        
    def populate_tree(self, parent, root_path, recurse = True):
        """
        Lazily populate the TreeListCtrl for given item
        
        parent(TreeListItem) : The parent node in the tree
        root_path(str) : The absolute path in the OS to the 
        recurse(bool) : Keep going deeper into the tree
        """
        day_fmt = " %m-%d-%y "
        time_fmt = " %I:%M:%S %p "
        
        # Don't add again children if parent item is already expanded
        # But DO recurse into directories contained by the parent directory
        data = self.tree.GetItemData(parent)
        if data is None or data.expanded == False:
        
            # We are going to populate!
            
            # Get the directories and files contained in this folder
            dirs, files = dirs_and_files(root_path)
            
            for dirname in dirs:
                child = self.tree.AppendItem(parent, dirname, self.fldridx, self.fldropenidx)
                self.tree.SetItemText(child, col_size, "")
                try:
                    complete_path = os.path.join(root_path, dirname)
                    mtime = time.localtime(os.path.getmtime(complete_path))
                except PermissionError:
                    continue
                self.tree.SetItemText(child, col_day, str(time.strftime(day_fmt, mtime)))
                self.tree.SetItemText(child, col_time, str(time.strftime(time_fmt, mtime)))
            
            for file in files:
                child = self.tree.AppendItem(parent, file, self.fileidx, self.fileidx)
                complete_path = os.path.join(root_path, file)
                filesize_kb = os.path.getsize(complete_path)
                self.tree.SetItemText(child, col_size, '{:,}'.format(filesize_kb))
                mtime = time.localtime(os.path.getmtime(complete_path))
                self.tree.SetItemText(child, col_day, str(time.strftime(day_fmt, mtime)))
                self.tree.SetItemText(child, col_time, str(time.strftime(time_fmt, mtime)))
            
        # The top level has been populated if needed, now recurse into 
        # directories and populate them one more level
        if recurse:
            # Visit all the children of the parent
            item = self.tree.GetFirstChild(parent)
            while item.IsOk():
                if self.ItemIsDirectory(item):
                    # Go down one more level
                    self.populate_tree(item, self.ItemToAbsPath(item), recurse = False)
                # Go to next sibling in this directory
                item = self.tree.GetNextSibling(item)
            
        # Set the flag telling you that the parent has been populated
        data = NodeData()
        data.expanded = True
        self.tree.SetItemData(parent, data)
        
    def refresh_tree(self, parent, root_path, recurse = True):
        """
        Lazily refresh the TreeListCtrl for given item
        
        parent(TreeListItem) : The parent node in the tree
        root_path(str) : The absolute path in the OS to the 
        recurse(bool) : Keep going deeper into the tree
        """
        day_fmt = " %m-%d-%y "
        time_fmt = " %I:%M:%S %p "
        
        # We are going to populate!
        
        # Get the directories and files contained in this folder
        dirs, files = dirs_and_files(root_path)
        abspaths = [os.path.join(root_path, f) for f in files] + [os.path.join(root_path, d) for d in dirs]
        
        # Current children of this node
        item = self.tree.GetFirstChild(parent)
        children = []
        while item.IsOk():
            # Add this child
            children.append(item)
            # Go to next sibling in this directory
            item = self.tree.GetNextSibling(item)
        
        # First remove things that no longer exist
        for child in children:
            if self.ItemToAbsPath(child) not in abspaths:
                self.tree.DeleteItem(child)
                
        # Current children of this node
        item = self.tree.GetFirstChild(parent)
        children = []
        while item.IsOk():
            # Add this child
            children.append(item)
            # Go to next sibling in this directory
            item = self.tree.GetNextSibling(item)
        children_paths = [self.ItemToAbsPath(child) for child in children]
        
        # Then add things that are new
        for dirname in dirs:
            complete_path = os.path.join(root_path, dirname)
            try:
                mtime = time.localtime(os.path.getmtime(complete_path))
            except PermissionError:
                continue
            
            if complete_path not in children_paths:
                child = self.tree.AppendItem(parent, dirname, self.fldridx, self.fldropenidx)
            else:
                child = children[children_paths.index(complete_path)]
                
            self.tree.SetItemText(child, col_size, "")
            self.tree.SetItemText(child, col_day, str(time.strftime(day_fmt, mtime)))
            self.tree.SetItemText(child, col_time, str(time.strftime(time_fmt, mtime)))
        
        for file in files:
            complete_path = os.path.join(root_path, file)
            
            if complete_path not in children_paths:
                child = self.tree.AppendItem(parent, file, self.fileidx, self.fileidx)
            else:
                child = children[children_paths.index(complete_path)]
            
            filesize_kb = os.path.getsize(complete_path)
            self.tree.SetItemText(child, col_size, '{:,}'.format(filesize_kb))
            mtime = time.localtime(os.path.getmtime(complete_path))
            self.tree.SetItemText(child, col_day, str(time.strftime(day_fmt, mtime)))
            self.tree.SetItemText(child, col_time, str(time.strftime(time_fmt, mtime)))
            
        # The top level has been populated if needed, now recurse into 
        # directories and populate them one more level
        if recurse:
            # Visit all the children of the parent
            item = self.tree.GetFirstChild(parent)
            while item.IsOk():
                if self.ItemIsDirectory(item):
                    # Go down one more level
                    self.populate_tree(item, self.ItemToAbsPath(item), recurse = False)
                # Go to next sibling in this directory
                item = self.tree.GetNextSibling(item)
            
        # Set the flag telling you that the parent has been populated
        data = NodeData()
        data.expanded = True
        self.tree.SetItemData(parent, data)
    
    def OnHeaderClick(self, event):
        sorted, col, ascendingOrder = self.tree.GetSortColumn()
        if event.GetColumn() == col and sorted and ascendingOrder == True:
            event.Veto()
        else:
            event.Skip()
        
    def ItemToAbsPath(self, item):
        """ Build the absolute path to the item by walking back up the tree"""
        parts = []
        while item.IsOk():
            # Get the next part
            part = self.tree.GetItemText(item, col_tree)
            if part:
                # Prepend this part
                parts.insert(0, part)
            # Walk up the tree one level
            item = self.tree.GetItemParent(item)
            
        path = os.path.sep.join(parts)
        return path
        
    def ItemIsDirectory(self, item):
        return os.path.isdir(self.ItemToAbsPath(item))
        
    def OnExpandTreeListLeaf(self, event):
        items = self.tree.GetSelections()
        if items:
            fname = self.tree.GetItemText(items[0], col_tree)
            self.populate_tree(items[0], self.ItemToAbsPath(items[0]))
        
    def OnTreeDoubleClick(self, event):
        if 'win' in sys.platform:
            items = self.tree.GetSelections()
            if len(items) != 1: 
                ErrorMessage("Must select one thing in tree")
                return
            path = self.ItemToAbsPath(items[0])
            if path.upper().endswith('.EXE'):
                self.OnRunExecutable(event)
        else:
            logging.log('Cannot double-click launch on non-windows platform')
        
    def OnTreeChar(self, event = None):
        keycode = event.GetKeyCode()
        
        # Toggle the value in the first column
        def toggle(items):
            for s in items:
                if self.tree.GetCheckedState(s) == wx.CHK_CHECKED:
                    self.tree.CheckItem(s, wx.CHK_UNCHECKED)
                else:
                    self.tree.CheckItem(s, wx.CHK_CHECKED)
        if keycode == ord('*'):
            if len(self.tree.GetSelections()) != 1:
                wx.LogMessage("Can only select one item for glob select with *")
                
            item = self.tree.GetSelections()[0]
            # Get the file extension of the selected entity    
            root_ext = os.path.splitext(self.tree.GetItemText(item, col_tree))[1]
            
            if self.ItemIsDirectory(item):
                wx.LogMessage("Can only apply glob select to files")
                return
            
            # Rewind to the first sibling
            parent = self.tree.GetItemParent(item)
            item = self.tree.GetFirstChild(parent)
                
            while item.IsOk():
                fname = self.tree.GetItemText(item, col_tree)
                
                ext = os.path.splitext(fname)[1]
                if ext and ext.upper() == root_ext.upper() and not self.ItemIsDirectory(item):
                    toggle([item])
                item = self.tree.GetNextSibling(item)
        else:
            event.Skip()
                    
    def OnTreeKeyPress(self, event = None):
        # Toggle the value in the first column
        def toggle(items):
            for s in items:
                if self.tree.GetCheckedState(s) == wx.CHK_CHECKED:
                    self.tree.CheckItem(s, wx.CHK_UNCHECKED)
                else:
                    self.tree.CheckItem(s, wx.CHK_CHECKED)
            
        keycode = event.GetUnicodeKey()
        if keycode == wx.WXK_SPACE or keycode == ' ':
            if len(self.tree.GetSelections()) > 1:
                for s in self.tree.GetSelections():
                    if self.ItemIsDirectory(s):
                        wx.LogMessage("Cannot use directory as part of multi-select")
                        return
                toggle(self.tree.GetSelections())
                
            else:
                item = self.tree.GetSelections()[0] # Only one selection for sure
                if not self.ItemIsDirectory(item):
                    toggle(self.tree.GetSelections())
                else:
                    # If a directory, select all files (but not subdirectories) in the directory
                    item = self.tree.GetFirstChild(item)
                    
                    while item.IsOk():
                        if self.ItemIsDirectory(item): 
                            item = self.tree.GetNextSibling(item)
                            continue
                        else:
                            toggle([item])
                        item = self.tree.GetNextSibling(item)
        elif event.GetKeyCode() == wx.WXK_ESCAPE:
            self.UncheckAllItems()
            event.Skip()
        else:
            #print keycode, type(keycode), event.GetKeyCode(), wx.WXK_ESCAPE
            event.Skip()
        
    def set_accelerators(self):
        # See http://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/
        accelEntries = []
        for key in ['F', 'D', 'A', 'X', 'R', 'O']:

            eventId = wx.NewId()
            accelEntries.append( (wx.ACCEL_NORMAL, ord(key), eventId) )

            self.Bind(wx.EVT_MENU, lambda evt, _id=eventId, _key = key: self.OnManualAccelerator(evt, _id, _key), id=eventId)
        
        accelTable  = wx.AcceleratorTable(accelEntries)
        self.SetAcceleratorTable(accelTable )
                
    def OnManualAccelerator(self, event, id, key):
        #for menu, label in self.menuBar.GetMenus():
        #    print menu, label, menu.GetWindow().GetScreenPosition(), menu.GetWindow().GetScreenPosition()
        #print self.menuBar.GetScreenPosition()
        
        treepos = self.tree.GetScreenPosition()
        #print treepos
        coords = self.ScreenToClient(treepos)
        if key == 'F':
            mnu = self.make_file_menu(self)
        elif key == 'D':
            mnu = self.make_directory_menu(self)
        elif key == 'A':
            mnu = self.make_applications_menu(self)
        elif key == 'O':
            mnu = self.make_options_menu(self)
        elif key == 'R':
            self.OnRunExecutable(self)
            return
        elif key == 'X':
            self.OnClose()
            return
        else:
            ErrorMessage("Not able to process keystroke " + key)
        self.PopupMenu(mnu, wx.Point(coords.x, coords.y))
        
    def OnWriteDirectory(self, event):
        
        item = self.tree.GetFirstChild(self.root)
            
        text = []
        while item.IsOk():
            if self.tree.IsVisible(item):
        
                name = self.tree.GetItemText(item, col_tree)
                size = self.tree.GetItemText(item, col_size)
                day = self.tree.GetItemText(item, col_day)
                time = self.tree.GetItemText(item, col_time)
                line = "{name:s}\t{size:s}\t{day:s}\t{time:s}".format(**locals())
                text.append(line)
                
            item = self.tree.GetNext(item)
        
        dlg = wx.FileDialog(
            self, message="Select output file", defaultDir=os.getcwd(), 
            defaultFile="", style=wx.SAVE
            )

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            with open(path, 'w') as fp:
                fp.write('\n'.join(text))
        dlg.Destroy()
    
    def UncheckAllItems(self):        
        item = self.root
        items = []
        while item.IsOk():
            self.tree.CheckItem(item, wx.CHK_UNCHECKED)
            item = self.tree.GetNextItem(item)
        return items
        
    def GetMarkedItems(self):        
        item = self.tree.GetFirstChild(self.root)
        items = []
        while item.IsOk():
            if self.tree.GetCheckedState(item) == wx.CHK_CHECKED:
                items.append(item)
            item = self.tree.GetNextItem(item)
        return items

    def OnRemoveDirectory(self, event):
        items = self.tree.GetSelections()
        if len(items) != 1: 
            ErrorMessage("Must select one thing in tree")
            return
        parent = self.tree.GetItemParent(items[0])
        path = self.ItemToAbsPath(items[0])
        if not self.ItemIsDirectory(items[0]): 
            ErrorMessage("Selected entity is not a directory")
            return
        try:
            os.rmdir(path)
        except OSError:
            ErrorMessage("Selected directory is not empty")
            
        self.refresh_tree(parent, self.ItemToAbsPath(parent))
        
    def OnRemoveFile(self, event):
        items = self.GetMarkedItems()
        if len(items) == 0: 
            ErrorMessage("Must select at least one file in tree")
            return
        if any([self.ItemIsDirectory(item) for item in items]):
            ErrorMessage("Cannot remove directories")
            return
        parents = []
        for item in items:
            parents.append(self.tree.GetItemParent(item))
            path = self.ItemToAbsPath(item)
            try:
                os.remove(path)
            except OSError:
                ErrorMessage("Cannot remove file" + path)
            
        for parent in set(parents):
            self.refresh_tree(parent, self.ItemToAbsPath(parent))
    
    def OnRenameFile(self, event):
        items = self.tree.GetSelections()
        if len(items) != 1: 
            ErrorMessage("Must select one thing in tree")
            return
        fname = self.tree.GetItemText(items[0],col_tree)
        parent = self.tree.GetItemParent(items[0])
        path = self.ItemToAbsPath(items[0])
        if self.ItemIsDirectory(items[0]): 
            ErrorMessage("Selected entity is not a file")
            return
        dlg = wx.TextEntryDialog(
                self, 'New file name',
                'Was ' + path, fname)
                
        if dlg.ShowModal() == wx.ID_OK:
            try:
                os.rename(path, os.path.join(os.path.dirname(path), dlg.GetValue()))
                self.refresh_tree(parent, self.ItemToAbsPath(parent))
            except OSError:
                ErrorMessage("Rename target already exists")
        dlg.Destroy()
        
    def OnRenameDirectory(self, event):
        items = self.tree.GetSelections()
        if len(items) != 1: 
            ErrorMessage("Must select one thing in tree")
            return
        fname = self.tree.GetItemText(items[0],col_tree)
        parent = self.tree.GetItemParent(items[0])
        path = self.ItemToAbsPath(items[0])
        if not self.ItemIsDirectory(items[0]): 
            ErrorMessage("Selected entity is not a directory")
            return
        dlg = wx.TextEntryDialog(
                self, 'New directory name',
                'Was' + path, fname)
                
        if dlg.ShowModal() == wx.ID_OK:
            try:
                os.rename(path, os.path.join(os.path.dirname(path),dlg.GetValue()))
                self.refresh_tree(parent, self.ItemToAbsPath(parent))
            except OSError:
                ErrorMessage("Rename target already exists")
        dlg.Destroy()
        
    def OnNewDirectory(self, event):
        """ Event handler to make a new directory """
        items = self.tree.GetSelections()
        if len(items) != 1: 
            ErrorMessage("Must select one thing in tree")
        path = self.ItemToAbsPath(items[0])
        if os.path.isdir(path):
            # Put it in the directory selected
            root = path
            parent = items[0]
        else:
            # Put it in the containing folder
            root = os.path.dirname(path)
            parent = self.tree.GetItemParent(items[0])
            
        dlg = wx.TextEntryDialog(
                self, 'New directory to be added to ' + root,
                'New directory', '')
        if dlg.ShowModal() == wx.ID_OK:
            fname = os.path.join(root, dlg.GetValue())
            if os.path.exists(fname):
                ErrorMessage("Cannot create directory [{fname:s}] as it already exists".format(fname = fname))
            else:
                os.mkdir(fname)
                self.refresh_tree(parent, self.ItemToAbsPath(parent))
        dlg.Destroy()
        
    def OnFileNew(self, event):
        """ Event handler to make a new file """
        items = self.tree.GetSelections()
        if len(items) != 1: 
            ErrorMessage("Must select one thing in tree")
        path = self.ItemToAbsPath(items[0])
        if os.path.isdir(path):
            # Put it in the directory selected
            root = path
            parent = items[0]
        else:
            # Put it in the containing folder
            root = os.path.dirname(path)
            parent = self.tree.GetItemParent(items[0])
        
        dlg = wx.TextEntryDialog(
                self, 'New file to be added to ' + root,
                'New directory', '')
        if dlg.ShowModal() == wx.ID_OK:
            fname = os.path.join(root, dlg.GetValue())
        else:
            fname = None
        dlg.Destroy()
        if fname is None:
            return
        if os.path.exists(fname):
            ErrorMessage("Cannot create file [{fname:s}] as it already exists".format(fname = fname))
        # Make the file
        with open(fname, 'w'):
            pass
        self.refresh_tree(parent, self.ItemToAbsPath(parent))
        
    def OnLoadBookmark(self, event, path):
        self.root_path = path
        self.build_tree()
            
    def OnChangePath(self, event):
        # In this case we include a "New directory" button. 
        dlg = wx.DirDialog(self, "Choose a directory:",
                          style=wx.DD_DEFAULT_STYLE
                           #| wx.DD_DIR_MUST_EXIST
                           #| wx.DD_CHANGE_DIR
                           )

        # If the user selects OK, then we process the dialog's data.
        # This is done by getting the path data from the dialog - BEFORE
        # we destroy it. 
        if dlg.ShowModal() == wx.ID_OK:
            self.root_path = dlg.GetPath()
            self.build_tree()

        # Only destroy a dialog after you're done with it.
        dlg.Destroy()
        
    def OnChangeDrive(self, event):
        if 'win' in sys.platform:
            try:
                import win32api
            except ImportError:
                ErrorMessage("Unable to import win32api")
                return
        else:
            print('no such thing as drive on this platform')

        drives = win32api.GetLogicalDriveStrings()
        drives = drives.split('\000')[:-1]
        
        dlg = wx.SingleChoiceDialog(
                self, 'Select Working Drive:', 'Drive?',
                drives, 
                wx.CHOICEDLG_STYLE
                )

        if dlg.ShowModal() == wx.ID_OK:
            self.root_path = dlg.GetStringSelection()
            self.build_tree()

        dlg.Destroy()
        
    def OnChar(self, event):
        keycode = event.GetUnicodeKey()
        if keycode != wx.WXK_NONE:
            # It's a printable character
            wx.LogMessage("You pressed '%c'"%keycode)
        else:
            wx.LogMessage("You pressed a non ASCII key '%c'"%keycode)
    
    def OnFileCopy(self, event = None):
        source_items = self.GetMarkedItems()
        if len(source_items) == 0: 
            ErrorMessage("At least one file must be marked")
            return
        items = self.tree.GetSelections()
        if len(items) != 1:
            ErrorMessage("One target directory must be selected")
            return
        if not self.ItemIsDirectory(items[0]):
            ErrorMessage("Target must be a directory")
            return
        new_dir = self.ItemToAbsPath(items[0])
        # Prepare paths (to make sure we don't have collision)
        paths, parents = [], []
        for item in source_items:
            parents.append(self.tree.GetItemParent(item))
            old_path = self.ItemToAbsPath(item)
            old_fname = self.tree.GetItemText(item, col_tree)
            new_path = os.path.join(new_dir, old_fname)
            paths.append((old_path, new_path))
        old, new = zip(*paths)
        if len(paths) != len(set(new)):
            ErrorMessage("At least two destination files have the same name")
            return
        if any([os.path.exists(n) for n in new]):
            dlg = wx.MessageDialog(self, 'Some output files will be over-written, Yes to continue and over-write',
                               'Over-write?',
                               wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION
                               )
            if dlg.ShowModal() == wx.ID_NO:                
                dlg.Destroy()
                return
        # And here goes the copy
        for (old, new) in paths:
            shutil.copy2(old, new)
            
        for parent in set(parents):
            self.refresh_tree(parent, self.ItemToAbsPath(parent))
        
    def OnFileMove(self, event = None):
        source_items = self.GetMarkedItems()
        if len(source_items) == 0: 
            ErrorMessage("At least one file must be marked")
            return
        items = self.tree.GetSelections()
        if len(items) != 1:
            ErrorMessage("One target directory must be selected")
            return
        if not self.ItemIsDirectory(items[0]):
            ErrorMessage("Target must be a directory")
            return
        new_dir = self.ItemToAbsPath(items[0])
        # Prepare paths (to make sure we don't have collision)
        paths, parents = [], []
        for item in source_items:
            parents.append(self.tree.GetItemParent(item))
            old_path = self.ItemToAbsPath(item)
            old_fname = self.tree.GetItemText(item, col_tree)
            new_path = os.path.join(new_dir, old_fname)
            paths.append((old_path, new_path))
        old, new = zip(*paths)
        print(old, new)
        if len(paths) != len(set(new)):
            ErrorMessage("At least two destination files have the same name")
            return
        if any([os.path.exists(n) for n in new]):
            dlg = wx.MessageDialog(self, 'Some output files will be over-written, Yes to continue and over-write',
                               'Over-write?',
                               wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION
                               )
            if dlg.ShowModal() == wx.ID_NO:
                dlg.Destroy()
                return
        # And here goes the move
        for (old, new) in paths:
            shutil.move(old, new)
            
        for parent in set(parents):
            self.refresh_tree(parent, self.ItemToAbsPath(parent))
    
    def OnClose(self, evnt = None):
        dlg = wx.MessageDialog(self, 'Quit?',
                               'Quit?',
                               wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION
                               )
        if dlg.ShowModal() == wx.ID_YES:
            pref_path = os.path.join(app_root_path, 'preferences.json')
            if os.path.exists(pref_path):
                with open(pref_path, 'r') as fp:
                    options = json.load(fp)
                    options['size'] = list(self.GetSize())
                with open(pref_path, 'w') as fp:
                    json_string = json.dumps(options)
                    fp.write(json_string)
                    
            self.Destroy()
        dlg.Destroy()
    
    def OnModifyBookmarks(self, event = None):
        dlg = ModifyBookmarksDialog()
        dlg.ShowModal()
        dlg.Destroy()
        
    def OnModifyApplications(self, event = None):
        dlg = ModifyApplicationsDialog()
        dlg.ShowModal()
        dlg.Destroy()
    
    def OnApplicationLaunch(self, event, menu):
        items = self.tree.GetSelections()
        if len(items) != 1:
            ErrorMessage("One file must be selected")
            return
        file_path = os.path.normpath(self.ItemToAbsPath(items[0]))
        name = menu.GetLabel()
            
        app_path = None
        for app in self.apps:
            if app['name'].replace('&','') == name: # Remove accelerator symbol
                app_path = app['path']
                break
        if app_path is None:
            ErrorMessage("Could not match app name: " + name)
            return
            
        # See http://stackoverflow.com/a/12144179/1360263
        # define a command that starts new terminal
        if platform.system() == "Windows":
            if hasattr(sys, 'frozen'):
                new_window_command = "cmd /C "
            else:
                new_window_command = ""
        else:  #XXX this can be made more portable
            new_window_command = "x-terminal-emulator -e".split()
            
        command = ' '.join(['"' + p + '"' for p in [app_path, file_path]])
        
        #if hasattr(sys, 'frozen'):
        #    call_string = new_window_command + '" ' + command + ' "'
        #else:
        call_string = command
        print('calling', call_string)
        subprocess.Popen(call_string, cwd = os.path.dirname(file_path), shell = False)
        
    def OnRunExecutable(self, event):
    
        items = self.tree.GetSelections()
        if len(items) != 1:
            ErrorMessage("One file must be selected")
            return
        exe_path = self.ItemToAbsPath(items[0])
        
        # See http://stackoverflow.com/a/12144179/1360263
        # define a command that starts new terminal
        if platform.system() == "Windows":
            new_window_command = "cmd.exe /c "
        else:  #XXX this can be made more portable
            new_window_command = "x-terminal-emulator -e".split()
        
        dlg = RunExecutableDialog(os.path.dirname(exe_path), False)
        if dlg.ShowModal() == wx.ID_OK:
            path, new_console, arguments = dlg.get()
            dlg.Destroy()
            # Check if the output path is invalid
            if path is None and new_console is None:
                return
        else:
            dlg.Destroy()
            return
        
        if arguments:
            arguments = ' '.join(['"' + arg + '"' for arg in arguments if arg])
        else:
            arguments = ''
            
        exe_path = '"' + exe_path + '"'
            
        call_string = new_window_command + '" ' + exe_path + arguments + ' "'
        if new_console:
            creationflags = CREATE_NEW_CONSOLE
        else:
            creationflags = 0
        subprocess.Popen(call_string, cwd = path, shell = False, creationflags=creationflags)
        
    def make_options_menu(self, parent):
        """
        As the name implies, construct the options menu and return it
        """
        def set_bookmarks():
            book_path = os.path.join(app_root_path, 'bookmarks.json')
            if os.path.exists(book_path):
                bookmarks = json.load(open(book_path, 'r'))
                for i,mark in enumerate(bookmarks):
                    menu.BookMarks[i].SetText('&' + str(i+1)+ ': ' + mark['name'])
                    parent.Bind(wx.EVT_MENU, lambda evt, path = mark['path']: parent.OnLoadBookmark(evt, path), menu.BookMarks[i])
                    
        menu = wx.Menu()
        menu.Refresh = wx.MenuItem(menu, -1, "&Refresh\tF5", "", wx.ITEM_NORMAL)
        menu.sep = wx.MenuItem(menu, -1, "", "", wx.ITEM_SEPARATOR)
        menu.book = wx.MenuItem(menu, -1, "**Bookmarks**", "", wx.ITEM_NORMAL)
        menu.BookMarks = []
        for i in range(10):
            menu.BookMarks.append(wx.MenuItem(menu, -1, " ", " ", wx.ITEM_NORMAL))
        menu.ModifyBookmarks = wx.MenuItem(menu, -1, "Modify...", "", wx.ITEM_NORMAL)
        
        for el in [menu.Refresh, menu.sep, menu.book] + menu.BookMarks + [menu.ModifyBookmarks]:
            if wx_phoenix:
                menu.Append(el)
            else:
                menu.AppendItem(el)
        parent.Bind(wx.EVT_MENU, lambda evt: self.build_tree(), menu.Refresh)
        parent.Bind(wx.EVT_MENU, parent.OnModifyBookmarks, menu.ModifyBookmarks)
        set_bookmarks()
        return menu
        
    def make_directory_menu(self, parent):
        """
        As the name implies, construct the directory menu and return it
        """
        menu = wx.Menu()
        menu.Drive = wx.MenuItem(menu, -1, "Change &Drive", "", wx.ITEM_NORMAL)
        menu.Path = wx.MenuItem(menu, -1, "Change &Path", "", wx.ITEM_NORMAL)
        menu.Sort = wx.MenuItem(menu, -1, "&Sort (WIP)", "", wx.ITEM_NORMAL)
        menu.New = wx.MenuItem(menu, -1, "&New", "", wx.ITEM_NORMAL)
        menu.Erase = wx.MenuItem(menu, -1, "&Erase", "", wx.ITEM_NORMAL)
        menu.Rename = wx.MenuItem(menu, -1, "&Rename", "", wx.ITEM_NORMAL)
        menu.Write = wx.MenuItem(menu, -1, "&Write", "", wx.ITEM_NORMAL)
        for el in [menu.Drive, menu.Path, menu.Sort, menu.New, menu.Erase, menu.Rename, menu.Write]:
            if wx_phoenix:
                menu.Append(el)
            else:
                menu.AppendItem(el)
        parent.Bind(wx.EVT_MENU, parent.OnNewDirectory, menu.New)
        parent.Bind(wx.EVT_MENU, parent.OnRemoveDirectory, menu.Erase)
        parent.Bind(wx.EVT_MENU, parent.OnRenameDirectory, menu.Rename)
        parent.Bind(wx.EVT_MENU, parent.OnChangePath, menu.Path)
        parent.Bind(wx.EVT_MENU, parent.OnChangeDrive, menu.Drive)
        parent.Bind(wx.EVT_MENU, parent.OnWriteDirectory, menu.Write)
        return menu
    
    def make_file_menu(self, parent):
        """
        As the name implies, construct the file menu and return it
        """
        menu = wx.Menu()
        menu.Find = wx.MenuItem(menu, -1, "&Find (WIP)", "", wx.ITEM_NORMAL)
        menu.Copy = wx.MenuItem(menu, -1, "&Copy", "", wx.ITEM_NORMAL)
        menu.Rename = wx.MenuItem(menu, -1, "&Rename", "", wx.ITEM_NORMAL)
        menu.Move = wx.MenuItem(menu, -1, "&Move", "", wx.ITEM_NORMAL)
        menu.Erase = wx.MenuItem(menu, -1, "&Erase", "", wx.ITEM_NORMAL)
        menu.New = wx.MenuItem(menu, -1, "&New", "", wx.ITEM_NORMAL)
        menu.Attrib = wx.MenuItem(menu, -1, "&Attrib (WIP)", "", wx.ITEM_NORMAL)
        for el in [menu.Find, menu.Copy, menu.Rename, menu.Move, menu.New, menu.Erase, menu.Attrib]:
            if wx_phoenix:
                menu.Append(el)
            else:
                menu.AppendItem(el)
        parent.Bind(wx.EVT_MENU, parent.OnFileCopy, menu.Copy)
        parent.Bind(wx.EVT_MENU, parent.OnFileMove, menu.Move)
        parent.Bind(wx.EVT_MENU, parent.OnFileNew, menu.New)
        parent.Bind(wx.EVT_MENU, parent.OnRemoveFile, menu.Erase)
        parent.Bind(wx.EVT_MENU, parent.OnRenameFile, menu.Rename)
        return menu
        
    def make_applications_menu(self, parent):
        """
        As the name implies, construct the applications menu and return it
        """
        menu = wx.Menu()
            
        apps_json_path = os.path.join(app_root_path, 'apps.json')
        if os.path.exists(apps_json_path):
            with open(apps_json_path, 'r') as fp:
                apps = json.load(fp)
                
            for app in apps:
                el = wx.MenuItem(menu, -1, app['name'], "", wx.ITEM_NORMAL)
                if wx_phoenix:
                    menu.Append(el)
                else:
                    menu.AppendItem(el)
                parent.Bind(wx.EVT_MENU, lambda evt, menu = el: parent.OnApplicationLaunch(evt, menu), el)
            self.apps = apps
        menu.Append(wx.MenuItem(menu, -1, "", "", wx.ITEM_SEPARATOR))
        menu.ModifyApplications = wx.MenuItem(menu, -1, "Modify...", "", wx.ITEM_NORMAL)
        menu.Append(menu.ModifyApplications)
        
        parent.Bind(wx.EVT_MENU, parent.OnModifyApplications, menu.ModifyApplications)
        
        return menu
        
    def make_menu_bar(self):
        """
        As the name implies, construct the menus and attach them all to the menubar
        """
        
        # Menu Bar
        self.menuBar = wx.MenuBar()

        # Build Run menu
        self.menuRun = wx.Menu()
        self.menuRunRun = wx.MenuItem(self.menuRun, -1, "&Run highlighted program", "", wx.ITEM_NORMAL)
        if wx_phoenix:
            self.menuRun.Append(self.menuRunRun)
        else:
            self.menuRun.AppendItem(self.menuRunRun)
        self.menuRunId = self.menuBar.Append(self.menuRun, "&Run")
        self.Bind(wx.EVT_MENU, lambda x, evt: x, self.menuRunRun)
        
        # Build File menu
        self.menuFile = self.make_file_menu(self)
        self.menuBar.Append(self.menuFile, "&File")
        
        # Build Directory menu
        self.menuDirectory = self.make_directory_menu(self)
        self.menuBar.Append(self.menuDirectory, "&Directory")
        
        # Build Application menu (empty to start)
        self.menuApplication = self.make_applications_menu(self)
        self.menuBar.Append(self.menuApplication, "&Application")
        
        # Build options menu
        self.menuOptions = self.make_options_menu(self)
        self.menuBar.Append(self.menuOptions, "&Options")
        
        # Exit menu
        self.menuExit = wx.Menu()
        self.menuExitExit = wx.MenuItem(self.menuExit, -1, "E&xit", "", wx.ITEM_NORMAL)
        if wx_phoenix:
            self.menuExit.Append(self.menuExitExit)
        else:
            self.menuExit.AppendItem(self.menuExitExit)
        self.menuBar.Append(self.menuExit, 'E&xit')
        
        #Actually set it
        self.SetMenuBar(self.menuBar)
Ejemplo n.º 6
0
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)