Esempio n. 1
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)
Esempio n. 2
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)