class Controller: """handle menu transitions, and act as gobetween for model/view""" def __init__(self): self.view = View() self.library = Library() self.player = Player(self.library) def handle_track_select(self): display = self.view.menu_stack[-1] selected_item = display.get_selected_item() path = selected_item.path name = os.path.basename(display.menu_path) if path == cfg.media_option_items[MediaOptions.PLAY]: self.player.play(display.menu_path) self.view.menu_stack.pop() self.view.notify(cfg.playing_str) elif path == cfg.media_option_items[MediaOptions.QUEUE_NEXT]: self.player.queue_next(display.menu_path) self.view.menu_stack.pop() self.view.notify(name + cfg.play_next_str) elif path == cfg.media_option_items[MediaOptions.QUEUE_LAST]: self.player.queue_last(display.menu_path) self.view.menu_stack.pop() self.view.notify(name + cfg.play_last_str) def handle_media_select(self, item_path: str, display: Display): items = [] for opt in cfg.media_option_items: items.append(DisplayItem(ItemType.Menu, opt)) new_display = Display(items, item_path) self.view.menu_stack.append(new_display) def handle_dir_select(self, item_path: str, display): item_name = item_path.split(os.sep)[-1] display_path = display.menu_path + os.sep + item_name item_list = self.library.get_disk_items(item_path) new_display = Display(item_list, display_path) self.view.menu_stack.append(new_display) def handle_queue_select(self): display_path = cfg.home_menu_items[HomeOptions.QUEUE] display_items = [] for item in self.player.next_tracks: display_items.append(DisplayItem(ItemType.Track, item)) new_display = Display(display_items, display_path) self.view.menu_stack.append(new_display) def handle_playlist_select(self, item, ext, display): tracks = self.library.get_playlist_tracks(display.menu_path) playlist = os.path.basename(display.menu_path) items = [] for track in tracks: items.append(DisplayItem(ItemType.Track, track)) if item.path == cfg.media_option_items[MediaOptions.VIEW]: new_display = Display(items, display.menu_path) self.view.menu_stack.append(new_display) elif item.path == cfg.media_option_items[MediaOptions.PLAY]: if not self.player.play(display.menu_path): self.view.notify(cfg.play_error_str) else: self.view.notify(cfg.playing_str) self.view.menu_stack.pop() elif item.path == cfg.media_option_items[MediaOptions.QUEUE_NEXT]: self.player.queue_next(tracks) self.view.menu_stack.pop() self.view.notify(playlist + cfg.play_next_str) elif item.path == cfg.media_option_items[MediaOptions.QUEUE_LAST]: self.player.queue_last(tracks) self.view.menu_stack.pop() self.view.notify(playlist + cfg.play_last_str) def handle_menu_select(self, item, ext, display): if ext in cfg.playlist_formats: self.handle_playlist_select(item, ext, display) if ext in cfg.music_formats: self.handle_track_select() def handle_lib_subset(self): curr_display = self.view.menu_stack[-1] menu_path = curr_display.menu_path key = curr_display.get_selected_item().path if cfg.home_menu_items[HomeOptions.ALBUMS] in menu_path: key_items = self.library.albums.get(key) elif cfg.home_menu_items[HomeOptions.ARTISTS] in menu_path: key_items = self.library.artists.get(key) elif cfg.home_menu_items[HomeOptions.GENRES] in menu_path: key_items = self.library.genres.get(key) if not key_items: self.view.notify(cfg.load_error_str) return else: new_item_list = [] for item in key_items: new_item_list.append(DisplayItem(ItemType.Track, item)) new_path = os.path.join(curr_display.menu_path, key) new_display = Display(new_item_list, new_path) self.view.menu_stack.append(new_display) def handle_album_select(self): path = cfg.home_menu_items[HomeOptions.ALBUMS] display_items = [] for key in self.library.albums.keys(): display_items.append(DisplayItem(ItemType.Directory, key)) display = Display(display_items, path) self.view.menu_stack.append(display) def handle_artist_select(self): path = cfg.home_menu_items[HomeOptions.ARTISTS] display_items = [] for key in self.library.artists.keys(): display_items.append(DisplayItem(ItemType.Directory, key)) display = Display(display_items, path) self.view.menu_stack.append(display) def handle_genre_select(self): path = cfg.home_menu_items[HomeOptions.GENRES] display_items = [] for key in self.library.genres: display_items.append(DisplayItem(ItemType.Directory, key)) display = Display(display_items, path) self.view.menu_stack.append(display) def handle_home_select(self): display = self.view.menu_stack[-1] index = display.index + display.start_index if index == HomeOptions.EXIT: return False elif index == HomeOptions.PLAYLISTS: path = cfg.home_menu_items[HomeOptions.PLAYLISTS] items = self.library.get_disk_items(cfg.playlist_dir) display = Display(items, path) self.view.menu_stack.append(display) elif index == HomeOptions.TRACKS: path = cfg.home_menu_items[HomeOptions.TRACKS] display = Display(self.library.get_tracks(), path) self.view.menu_stack.append(display) elif index == HomeOptions.ALBUMS: self.handle_album_select() elif index == HomeOptions.ARTISTS: self.handle_artist_select() elif index == HomeOptions.GENRES: self.handle_genre_select() elif index == HomeOptions.QUEUE: self.handle_queue_select() elif index == HomeOptions.SETTINGS: self.view.notify(cfg.not_implemented_str) return True def handle_select(self): display: Display = self.view.menu_stack[-1] item: DisplayItem = display.get_selected_item() if item is None: return ext: str = os.path.splitext(display.menu_path)[1] lib_subsets = [ cfg.home_menu_items[i] for i in (HomeOptions.ALBUMS, HomeOptions.GENRES) ] if not display.menu_path: return self.handle_home_select() elif display.menu_path in lib_subsets: self.handle_lib_subset() elif item.item_type is ItemType.Menu: self.handle_menu_select(item, ext, display) elif item.item_type is ItemType.Directory: self.handle_dir_select(item.path, display) elif item.item_type in (ItemType.Track, ItemType.Playlist): self.handle_media_select(item.path, display) def on_press(self, key: KeyCode): """Callback for handling user input.""" if hasattr(key, 'char'): if key.char == 'p': self.player.play() elif key.char == 'a': self.player.pause() elif key.char == 'n': self.player.skip_forward() elif key.char == 'l': self.player.skip_back() self.view.notify(self.player.get_state_str()) else: if key == Key.left: self.view.navigate_back() # can't navigate forward or select on an empty menu elif self.view.menu_stack[-1].items is None: return elif key == Key.up: self.view.navigate_up() elif key == Key.down: self.view.navigate_down() elif key == Key.right: return self.handle_select() def tick(self): """periodic ui update""" metadata = self.player.get_metadata() display = self.view.menu_stack[-1] if display.menu_path == cfg.home_menu_items[HomeOptions.QUEUE]: display_items = [] for item in self.player.next_tracks: display_items.append(DisplayItem(ItemType.Track, item)) new_display = display._replace(items=display_items) self.view.menu_stack.pop() self.view.menu_stack.append(new_display) self.view.update_status(metadata) self.view.update_menu() self.view.screen.refresh() def run(self): """splits into two threads for ui and pynput""" listener = Listener(on_press=self.on_press) try: listener.start() while listener.running: self.tick() sleep(cfg.refresh_rate) finally: del self.view del self.player del self.library