Source code for ui.path_picker

import os

from menu import Menu, MenuExitException, to_be_foreground
from printer import Printer
from helpers import setup_logger
logger = setup_logger(__name__, "warning")

[docs]class PathPicker(Menu): default_path = "/" path_chosen = None
[docs] def __init__(self, path, i, o, callback = None, name = None, file = None, display_hidden = False, dirs_only = False, append_current_dir = True, current_dot = False, prev_dot = True, scrolling=True, **kwargs): """Initialises the PathPicker object. Args: * ``path``: a path to start from. If path to a file is passed, will start from that file (unless overridden with ``file`` keyword argument). * ``i``, ``o``: input&output device objects. Kwargs: * ``callback``: if set, PathPicker will call the callback with path as first argument upon selecting path, instead of exiting the activate() * ``file``: if set, PathPicker will locate the file in the ``path`` passed and move its pointer to that file (provided it is found). * ``dirs_only``: if True, PathPicker will only show directories * ``append_current_dir``: if False, PathPicker won't add "Dir: %/current/dir%" first entry when `dirs_only` is enabled * ``current_dot``: if True, PathPicker will show '.' path * ``prev_dot``: if True, PathPicker will show '..' path * ``display_hidden``: if True, PathPicker will display hidden files """ Menu.__init__(self, [], i, o, entry_height=1, scrolling=True, append_exit=False, catch_exit=False, contents_hook=None, **kwargs) if not os.path.isdir(path): if os.path.exists(path): path, filename = os.path.split(path) # Picking file if file kwarg wasn't already passed file = filename if not file else file else: logger.warning("PathPicker path has to be a directory or a file that exists! Received {}, setting path to default path: {}".format(path, self.default_path)) self.base_name = name if name else "PathPicker" self.display_hidden = display_hidden self.callback = callback self.dirs_only = dirs_only self.append_current_dir = append_current_dir self.current_dot = current_dot self.prev_dot = prev_dot self.menu_pointers = {} self.set_path(os.path.normpath(path)) if file: self.move_to_file(file) self.update_keymap()
def before_activate(self): #Clearing flags Menu.before_activate(self) path_chosen = None def get_return_value(self): return self.path_chosen @to_be_foreground def select_entry(self): """ |Is typically used as a callback from input event processing thread. |After callback's execution is finished, sets the keymap again and refreshes the screen. |If menu has no elements, exits the menu. |If MenuExitException is returned from the callback, exits menu, too.""" logger.debug("element selected") if len(self.contents) > 0: self.to_background() self.contents[self.pointer][1]() self.to_foreground() if self.path_chosen: self.deactivate() else: self.to_foreground() def update_keymap(self): """Updates the keymap.""" self.keymap.update({ "KEY_RIGHT":lambda: self.options_menu(), "KEY_LEFT": lambda: self.go_back() }) def go_back(self): if self.path == '/': self.deactivate() return parent_path = os.path.split(self.path)[0] self.goto_dir(parent_path) def set_path(self, path): self.path = path self.name = "{}-{}".format(self.base_name, self.path) self.set_contents(self.regenerate_contents()) def regenerate_contents(self): contents = [] if self.dirs_only and self.append_current_dir: contents.append(["Dir: {}".format(self.path), lambda x=self.path: self.select_path(x)]) self.pointer = 0 if self.path != '/': if self.current_dot or self.prev_dot: dot_path = os.path.join(self.path, '.') if self.current_dot: contents.append(['.', lambda: self.goto_dir(dot_path)]) if self.prev_dot: contents.append(['..', lambda: self.goto_dir(dot_path+'.')]) path_contents = os.listdir(self.path) files = [] dirs = [] for item in path_contents: full_path = os.path.join(self.path, item) if os.path.isdir(full_path): if not (self.display_hidden and item.startswith('.')): dirs.append(item) else: if not (self.display_hidden and item.startswith('.')): if not self.dirs_only: files.append(item) dirs.sort() files.sort() for dir in dirs: full_path = os.path.join(self.path, dir) if self.dirs_only: isdir = lambda x: os.path.isdir(os.path.join(full_path, x)) dirs_in_dir = [isdir(e) for e in os.listdir(full_path)] if any(dirs_in_dir): #Directory has other directories inside contents.append([dir, lambda x=full_path: self.goto_dir(x), lambda: True]) else: #Directory has no other directories inside contents.append([dir, lambda x=full_path: self.select_path(x)]) else: contents.append([dir, lambda x=full_path: self.goto_dir(x), lambda: True]) for file in files: full_path = os.path.join(self.path, file) contents.append([file, lambda x=full_path: self.select_path(x)]) return contents def move_to_file(self, filename): for i, entry in enumerate(self.contents): label = entry[0] filepath = os.path.join(self.path, label) if filename == label and os.path.exists(filepath) and not os.path.isdir(filepath): self.pointer = i if self.in_foreground: self.refresh() return logger.warning("File {} not found in current path {}".format(filename, self.path)) @to_be_foreground def options_menu(self): self.to_background() current_item = self.contents[self.pointer][0] full_path = os.path.join(self.path, current_item) def get_contents(): dh_option_label = "Show .-files" if self.display_hidden else "Hide .-files" contents = [] if self.dirs_only: contents.append(["Select current dir", lambda x=self.path: self.option_select(x)]) contents += [["Select path",lambda x=full_path: self.option_select(x)], [dh_option_label, self.toggle_display_hidden], ["See full name",lambda x=full_path: Printer(current_item, self.i, self.o)], ["See full path",lambda x=full_path: Printer(x, self.i, self.o)], ["Exit PathPicker", self.option_exit]] return contents Menu([], self.i, self.o, contents_hook=get_contents, name="PathPicker context menu").activate() if self.in_background: self.set_contents(self.regenerate_contents()) self.to_foreground() def toggle_display_hidden(self): self.display_hidden = not self.display_hidden def option_exit(self): self.deactivate() raise MenuExitException #Menu needs to exit when PathPicker exits. It doesn't, of course, as it's in the main loop, so the app's left hanging. #One of the reasons MenuExitExceptions are there. def option_select(self, path): path = os.path.normpath(path) if self.callback is None: self.select_path(path) self.deactivate() raise MenuExitException else: self.callback(path) raise MenuExitException #@to_be_foreground def goto_dir(self, dir): dir = os.path.normpath(dir) self.menu_pointers[self.path] = self.pointer self.set_path(dir) if self.path in self.menu_pointers: pointer = self.menu_pointers[self.path] #If parent directory changed contents while we were browsing child directory, the count will be different #So, a quick check if pointer >= len(self.contents): self.pointer = len(self.contents)-1 else: self.pointer = pointer else: self.pointer = 0 self.view.refresh() #@to_be_foreground def select_path(self, path): path = os.path.normpath(path) if self.callback is not None: self.to_background() current_item = self.contents[self.pointer][0] full_path = os.path.join(self.path, current_item) self.callback(full_path) self.to_foreground() else: self.path_chosen = path