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 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)