omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    [Feature Request] Pythonista built-in file picker

    Pythonista
    4
    7
    5550
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Webmaster4o
      Webmaster4o last edited by Webmaster4o

      I think Pythonista is really missing something very beneficial in a built-in scriptable file navigator. The one that's inside the "move" dialog is really nice, but only supports folders, and plus, it's not user-accessible.

      I think Pythonista should have a filenav module that allows for easy creation of nice file navigators. They're pretty hard to write, IMHO, and so could benefit from being built-in. How I envision this is as follows:

      A filenav module that provides file browsers and navigators.
      1. Higher-level functions for picking files with callbacks. A function that would allow for picking files and folders. Something like:
      def pick_file(start_dir, folders_only=False, select_multiple=False, should_pick="both")
      

      where should_pick is a string that describes what should be selectable, "files", "folders", or "both" and folders_only is whether the file picker should only display folders, and hide files from view.
      2. A subclassable class like filenav.FilePicker. This would serve important functions:
      - Allow users to implement custom methods for file browsing. Users could define methods for listing directories, etc. A (far from complete) example:

      class myPicker(filenav.FilePicker):
      	def listdir(dir):
      		'''Should return a list of files and directories below a directory as a dictionary'''
      		list = os.listdir(dir)
      		dirs = [f for f in list if os.path.isdir(os.path.abspath(f))]
      		files = set(list)-set(dirs)
      		return {'dirs':dirs,'files':files}
      		
      	def fileSelected(path):
      		'''Called with the path of a file whenever a file is selected'''
      		pass
      		
      	def dirSelected(path):
      		'''Called with the path of a directory whenever a directory is selected'''
      		pass
      	
      
      if __name__=='__main__':
      	a=myPicker('test_dir/a/b/c')
      	#Picked will be a list of both files and directories that the user picked out of test_dir/a/b/c
      	picked = a.pick_file(multiple=True,should_pick='both')
      

      This doesn't immediately seem useful, but has tons of practical applications. For example, it would be easy, using a class like this, to create methods that would make a file browser for zip files. Using zipfile, custom methods could be created that allowed browsing through zip files without extracting first. This would allow a pythonista app to let users to pick certain files to extract from a zip.

      These two functionalities combined could help create a scriptable file browser that's easy for beginners, yet hugely powerful for more advanced users.

      I'd very much like to see a built-in file browser with these features in a future release.

      P.S. I actually have struggled to create a zip file browser in Pythonista in the past. I've gotten this far on GitHub, and Here's a sideways YouTube video

      1 Reply Last reply Reply Quote 8
      • ccc
        ccc last edited by

        @Webmaster4o This is a great idea. Your listdir() contains an awesome use of sets! It puts my files_and_folders.py to shame! You might want to put the is.listdir() inside a sorted() and using list as a variable name will shadow the builtin.

        1 Reply Last reply Reply Quote 1
        • omz
          omz last edited by

          I think this could be a useful addition to the dialogs module.

          1 Reply Last reply Reply Quote 6
          • omz
            omz last edited by omz

            I've started working on a file picker dialog that I might add to the dialogs module. You can find the code here:

            → File Picker.py (works in Pythonista 2 and 3)

            The API isn't exactly like you suggested, but similar, and quite extensible. As a demo, I've implemented an alternative "data source" that shows files and folders from an FTP server instead of the local file system. It would easily be possible to build a Zip file browser with this as well...

            The idea is basically that you can subclass TreeNode to represent whatever kind of tree structure you need. The TreeDialogController gets one TreeNode as its root and a couple of options (multiple selection etc.). The TreeNode is then responsible for loading its children (each of the children is also a TreeNode), and each node has a title/icon that is shown in the table view. For cases when a node might take a relatively long time to load its children, TreeDialogController has an async_mode option that causes it to automatically dispatch the loading to a background thread, and to show a spinner overlay while loading.

            Overall, the UI is very close to Pythonista's own directory picker, i.e. I'm using the same icons, table view animations, etc.

            Screenshot

            mikael 1 Reply Last reply Reply Quote 7
            • mikael
              mikael @omz last edited by

              @omz: Looks awesome. Would this be generic enough that the things in the tree do not actually have to be files? I.e. usable as a generic tree navigation component?

              1 Reply Last reply Reply Quote 1
              • omz
                omz last edited by omz

                @mikael Absolutely, as long as you can represent each item with a title and icon, the data can be pretty much anything. Here's an example of a TreeNode subclass that represents the contents of a JSON file (e.g. pyui):

                import json
                import sys
                PY3 = sys.version_info[0] >= 3
                if PY3:
                	basestring = str
                def iteritems(d):
                	if PY3:
                		return d.items()
                	else:
                		return d.iteritems()
                
                class JSONTreeNode (TreeNode):
                	def __init__(self, json_path=None, node=None, key=None, level=0):
                		TreeNode.__init__(self)
                		if json_path is not None:
                			with open(json_path, 'rb') as f:
                				root_node = json.load(f)
                				self.node = root_node
                		else:
                			self.node = node
                		if isinstance(self.node, list):
                			self.title = 'list (%i)' % (len(self.node),)
                			self.leaf = False
                			self.icon_name = 'iob:navicon_32'
                		elif isinstance(self.node, dict):
                			self.title = 'dict (%i)' % (len(self.node),)
                			self.leaf = False
                			self.icon_name = 'iob:ios7_folder_outline_32'
                		elif isinstance(self.node, basestring):
                			self.title = '"%s"' % (self.node,)
                			self.leaf = True
                			self.icon_name = 'iob:ios7_information_outline_32'
                		else:
                			self.title = str(self.node)
                			self.leaf = True
                			self.icon_name = 'iob:ios7_information_outline_32'
                		self.level = level
                		if key is not None:
                			self.title = key + ' = ' + self.title
                		elif json_path is not None:
                			self.title = os.path.split(json_path)[1]
                	
                	def expand_children(self):
                		if self.children is not None:
                			self.expanded = True
                			return
                		if isinstance(self.node, list):
                			self.children = [JSONTreeNode(node=n, level=self.level+1) for n in self.node]
                		elif isinstance(self.node, dict):
                			self.children = [JSONTreeNode(node=v, key=k, level=self.level+1) for k, v in iteritems(self.node)]
                		self.expanded = True
                

                Usage:

                root_node = JSONTreeNode(os.path.expanduser('~/Documents/Examples/User Interface/Calculator.pyui'))
                tree_controller = TreeDialogController(root_node)
                tree_controller.view.present('sheet')
                tree_controller.view.wait_modal()
                

                And here's how that looks like:

                Screenshot

                Webmaster4o 1 Reply Last reply Reply Quote 3
                • Webmaster4o
                  Webmaster4o @omz last edited by

                  @omz Are you still considering adding this to dialogs?

                  1 Reply Last reply Reply Quote 1
                  • First post
                    Last post
                  Powered by NodeBB Forums | Contributors