Beispiel #1
0
    def create_menus(self) -> None:
        """Create the menus used in each window, if necessary.

        Overrides method from Perspective; see documentation in that class.
        """
        assert all(window is not None
                   for window in [self._feed_window, self._episode_window])

        # this method could change a lot of screen content - probably
        # reasonable to simply clear the whole screen
        self._display.clear()

        # delete old menus if they exist
        if self._feed_menu is not None:
            del self._feed_menu
            self._feed_menu = None
        if self._episode_menu is not None:
            del self._episode_menu
            self._episode_menu = None

        feed_items = [[]]
        for key in self._display.feeds:
            feed = self._display.feeds[key]
            feed_items[0].append(str(feed))

        self._episode_menu = EpisodeMenu(self._episode_window,
                                         self._display._feeds)
        self._feed_menu = Menu(self._feed_window,
                               feed_items,
                               child=self._episode_menu,
                               active=True)

        # force reset active window to prevent starting in the episodes menu
        self._active_window = 0
Beispiel #2
0
def test_menu_episode_update_items(mock_color_pair, mock_A_NORMAL):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    source.episodes.assert_called_with(feed)
    time.sleep(1)  # hack to wait for thread
    assert len(mymenu._items) == 2
    assert len(mymenu) == 2
Beispiel #3
0
def test_menu_episode_items(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    items = mymenu._items()
    assert {
        'attr': mock_color_pair(5),
        'text': str(episode1)
    } in items
Beispiel #4
0
def test_menu_episode_items(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    items = mymenu._items
    assert {
        "attr": mock_color_pair(5),
        "tags": ["D"],
        "text": str(episode1)
    } in items
Beispiel #5
0
    def create_menus(self) -> None:
        """Create the menus used in each window."""
        assert all(window is not None
                   for window in [self._feed_window, self._episode_window])

        self._episode_menu = EpisodeMenu(self._episode_window,
                                         self._display.database)
        self._feed_menu = FeedMenu(self._feed_window,
                                   self._display.database,
                                   child=self._episode_menu,
                                   active=True)
    def create_menus(self) -> None:
        """Create the menus used in each window, if necessary.

        Overrides method from Perspective; see documentation in that class.
        """
        assert all(window is not None for window in [
            self._feed_window, self._episode_window
        ])

        self._episode_menu = EpisodeMenu(
            self._episode_window, self._display.database)
        self._feed_menu = FeedMenu(self._feed_window, self._display.database,
                                   child=self._episode_menu, active=True)
class PrimaryPerspective(Perspective):
    """The primary/default perspective.

    This class handles display elements while in the primary perspective, which
    is the default perspective.
    """
    ID = 1

    def __init__(self, display) -> None:
        """
        Overrides method from Perspective; see documentation in that class.
        """
        super().__init__(display)
        self._active_window = 0
        self._feed_window = None
        self._episode_window = None
        self._metadata_window = None
        self._feed_menu = None
        self._episode_menu = None
        self._metadata_updated = False

    def create_windows(self) -> None:
        """Create and set basic parameters for the windows.

        Overrides method from Perspective; see documentation in that class.
        """
        # delete old windows if they exist
        if self._feed_window is not None:
            del self._feed_window
            self._feed_window = None
        if self._episode_window is not None:
            del self._episode_window
            self._episode_window = None
        if self._metadata_window is not None:
            del self._metadata_window
            self._metadata_window = None

        parent_x = self._display.parent_x
        parent_y = self._display.parent_y
        third_x = helpers.third(parent_x)
        self._feed_window = curses.newwin(parent_y - 2, third_x, 2, 0)
        self._episode_window = curses.newwin(parent_y - 2, third_x, 2, third_x)
        metadata_width = parent_x - ((third_x * 2) - 1)
        self._metadata_window = curses.newwin(parent_y - 3, metadata_width, 2,
                                              2 * third_x)

        # update menus if necessary
        if self._feed_menu is not None:
            self._feed_menu.window = self._feed_window
        if self._episode_menu is not None:
            self._episode_menu.window = self._episode_window

    def create_menus(self) -> None:
        """Create the menus used in each window.

        Overrides method from Perspective; see documentation in that class.
        """
        assert all(window is not None
                   for window in [self._feed_window, self._episode_window])

        self._episode_menu = EpisodeMenu(self._episode_window,
                                         self._display.database)
        self._feed_menu = FeedMenu(self._feed_window,
                                   self._display.database,
                                   child=self._episode_menu,
                                   active=True)

    def display(self) -> None:
        """Draws all windows and sub-features, including titles and borders.

        Overrides method from Perspective; see documentation in that class.
        """
        # clear dynamic menu headers
        self._feed_window.addstr(0, 0, " " * self._feed_window.getmaxyx()[1])
        self._episode_window.addstr(0, 0,
                                    " " * self._episode_window.getmaxyx()[1])

        # add window headers
        self._feed_window.addstr(0, 0, self._feed_menu.title,
                                 curses.color_pair(7) | curses.A_BOLD)
        self._episode_window.addstr(0, 0, self._episode_menu.title,
                                    curses.color_pair(7) | curses.A_BOLD)
        self._metadata_window.addstr(0, 0, "Metadata",
                                     curses.color_pair(7) | curses.A_BOLD)

        # add window borders
        self._feed_window.hline(1, 0, 0,
                                self._feed_window.getmaxyx()[1],
                                curses.ACS_HLINE | curses.color_pair(8))
        self._episode_window.hline(1, 0, 0,
                                   self._episode_window.getmaxyx()[1],
                                   curses.ACS_HLINE | curses.color_pair(8))
        self._metadata_window.hline(1, 0, 0,
                                    self._metadata_window.getmaxyx()[1] - 1,
                                    curses.ACS_HLINE | curses.color_pair(8))
        if not helpers.is_true(Config["disable_vertical_borders"]):
            self._feed_window.vline(0,
                                    self._feed_window.getmaxyx()[1] - 1, 0,
                                    self._feed_window.getmaxyx()[0] - 2,
                                    curses.ACS_VLINE | curses.color_pair(8))
            self._episode_window.vline(0,
                                       self._episode_window.getmaxyx()[1] - 1,
                                       0,
                                       self._episode_window.getmaxyx()[0] - 2,
                                       curses.ACS_VLINE | curses.color_pair(8))

        # display menu content
        self._feed_menu.display()
        self._episode_menu.display()

        # draw metadata
        if not self._metadata_updated:
            self._draw_metadata(self._metadata_window)

    def handle_input(self, c) -> bool:
        """Performs action corresponding to the user's input.

        Overrides method from Perspective; see documentation in that class.
        """
        queue = self._display.queue
        key_mapping = self._display.KEY_MAPPING

        keep_running = True
        if c == key_mapping[Config['key_play_selected']]:
            queue.stop()
            queue.clear()
            self._create_player_from_selected()
            queue.play()
        elif c == key_mapping[Config['key_add_selected']]:
            self._create_player_from_selected()
            self._get_active_menu().move(-1)
        else:
            keep_running = self._generic_handle_input(c)

        return keep_running

    def made_active(self) -> None:
        """Called each time the perspective is made active (switched to).

        Overrides method from Perspective; see documentation in that class.
        """

    def update_menus(self) -> None:
        """Update/refresh the contents of all menus.

        Overrides method from Perspective; see documentation in that class.
        """
        self._feed_menu.update_items(None)
        self._feed_menu.update_child()

    def refresh(self) -> None:
        """Refresh the screen and all windows.

        Overrides method from Perspective; see documentation in that class.
        """
        self._feed_window.refresh()
        self._episode_window.refresh()
        self._metadata_window.refresh()
        self._feed_menu.refresh()

    def _get_active_menu(self) -> Menu:
        """Retrieve the active Menu, if there is one.

        Overrides method from Perspective; see documentation in that class.
        """
        assert 0 <= self._active_window < 2

        return {
            0: self._feed_menu,
            1: self._episode_menu,
        }.get(self._active_window)

    def _invert_selected_menu(self) -> None:
        """Inverts the contents of the selected menu.

        Overrides method from Perspective; see documentation in that class.
        """
        self._get_active_menu().invert()
        if self._feed_menu:
            self._feed_menu.update_child()

    def _create_player_from_selected(self) -> None:
        """Creates player(s) based on the selected items and adds to the queue.

        If the active menu is the feed menu, then this will create players for
        all episodes in the selected feed. If the active menu is the episode
        menu, this will simply create a single player.

        This method will not clear the queue prior to adding the new player(s),
        nor will it play the episodes after running.
        """
        if self._active_window == 0:
            feed = self._feed_menu.item
            if feed is not None:
                for episode in self._display.database.episodes(feed):
                    player = Player.create_instance(
                        self._display.AVAILABLE_PLAYERS, str(episode),
                        episode.get_playable(), episode)
                    self._display.queue.add(player)
        elif self._active_window == 1:
            episode = self._episode_menu.item
            if episode is not None:
                player = Player.create_instance(
                    self._display.AVAILABLE_PLAYERS, str(episode),
                    episode.get_playable(), episode)
                self._display.queue.add(player)
Beispiel #8
0
class PrimaryPerspective(Perspective):
    """The PrimaryPerspective class.

    This class handles display elements while in the primary perspective, which
    is the default perspective.
    """
    ID = 1

    def __init__(self, display) -> None:
        """Initializes the object.

        Overrides method from Perspective; see documentation in that class.
        """
        super().__init__(display)
        self._active_window = 0
        self._feed_window = None
        self._episode_window = None
        self._metadata_window = None
        self._feed_menu = None
        self._episode_menu = None
        self._metadata_updated = False

    def create_windows(self) -> None:
        """Create and set basic parameters for the windows.

        Overrides method from Perspective; see documentation in that class.
        """
        # delete old windows if they exist
        if self._feed_window is not None:
            del self._feed_window
            self._feed_window = None
        if self._episode_window is not None:
            del self._episode_window
            self._episode_window = None
        if self._metadata_window is not None:
            del self._metadata_window
            self._metadata_window = None

        parent_x = self._display.parent_x
        parent_y = self._display.parent_y
        third_x = helpers.third(parent_x)
        self._feed_window = curses.newwin(parent_y - 2, third_x, 2, 0)
        self._episode_window = curses.newwin(parent_y - 2, third_x, 2, third_x)
        metadata_width = parent_x - ((third_x * 2) - 1)
        self._metadata_window = curses.newwin(parent_y - 2, metadata_width, 2,
                                              2 * third_x)

        self._feed_window.attron(curses.color_pair(1))
        self._episode_window.attron(curses.color_pair(1))
        self._metadata_window.attron(curses.color_pair(1))

        # update menus if necessary
        if self._feed_menu is not None:
            self._feed_menu.window = self._feed_window
        if self._episode_menu is not None:
            self._episode_menu.window = self._episode_window

    def create_menus(self) -> None:
        """Create the menus used in each window.

        Overrides method from Perspective; see documentation in that class.
        """
        assert all(window is not None
                   for window in [self._feed_window, self._episode_window])

        self._episode_menu = EpisodeMenu(self._episode_window,
                                         self._display.database)
        self._feed_menu = FeedMenu(self._feed_window,
                                   self._display.database,
                                   child=self._episode_menu,
                                   active=True)

    def display(self) -> None:
        """Draws all windows and sub-features, including titles and borders.

        Overrides method from Perspective; see documentation in that class.
        """
        # add window titles
        self._feed_window.attron(curses.A_BOLD)
        self._episode_window.attron(curses.A_BOLD)
        self._metadata_window.attron(curses.A_BOLD)
        self._feed_window.addstr(0, 0, "Feeds")
        self._episode_window.addstr(0, 0, "Episodes")
        self._metadata_window.addstr(0, 0, "Metadata")

        # add window borders
        self._feed_window.hline(1, 0, 0, self._feed_window.getmaxyx()[1])

        self._episode_window.hline(1, 0, 0, self._episode_window.getmaxyx()[1])
        self._metadata_window.hline(1, 0, 0,
                                    self._metadata_window.getmaxyx()[1] - 1)
        if not helpers.is_true(Config["disable_vertical_borders"]):
            self._feed_window.vline(0,
                                    self._feed_window.getmaxyx()[1] - 1, 0,
                                    self._feed_window.getmaxyx()[0] - 2)
            self._episode_window.vline(0,
                                       self._episode_window.getmaxyx()[1] - 1,
                                       0,
                                       self._episode_window.getmaxyx()[0] - 2)

        # display menu content
        self._feed_menu.display()
        self._episode_menu.display()

        # draw metadata
        if not self._metadata_updated:
            self._draw_metadata(self._metadata_window)

    def handle_input(self, c) -> bool:
        """Performs action corresponding to the user's input.

        Overrides method from Perspective; see documentation in that class.
        """
        queue = self._display.queue
        key_mapping = self._display.KEY_MAPPING

        keep_running = True
        if c == key_mapping[Config['key_exit']]:
            self._display.terminate()
            keep_running = False
        elif c == key_mapping[Config['key_help']]:
            self._display.show_help()
        elif c == key_mapping[Config['key_right']]:
            self._change_active_window(1)
            self._metadata_updated = False
        elif c == key_mapping[Config['key_left']]:
            self._change_active_window(-1)
            self._metadata_updated = False
        elif c == key_mapping[Config['key_up']]:
            self._get_active_menu().move(1)
            self._metadata_updated = False
        elif c == key_mapping[Config['key_down']]:
            self._get_active_menu().move(-1)
            self._metadata_updated = False
        elif c == key_mapping[Config['key_scroll_up']]:
            self._get_active_menu().move_page(1)
            self._metadata_updated = False
        elif c == key_mapping[Config['key_scroll_down']]:
            self._get_active_menu().move_page(-1)
            self._metadata_updated = False
        elif c == key_mapping[Config['key_play_selected']]:
            queue.stop()
            queue.clear()
            self._create_player_from_selected()
            queue.play()
        elif c == key_mapping[Config['key_add_selected']]:
            self._create_player_from_selected()
            self._get_active_menu().move(-1)
        elif c == key_mapping[Config['key_clear']]:
            queue.stop()
            queue.clear()
        elif c == key_mapping[Config['key_pause_play']] or \
                c == key_mapping[Config['key_pause_play_alt']]:
            queue.toggle()
        elif c == key_mapping[Config['key_next']]:
            queue.stop()
            queue.next()
            queue.play()
        elif c == key_mapping[Config['key_seek_forward']] or \
                c == key_mapping[Config['key_seek_forward_alt']]:
            queue.seek(1)
        elif c == key_mapping[Config['key_seek_backward']] or \
                c == key_mapping[Config['key_seek_backward_alt']]:
            queue.seek(-1)
        elif c == key_mapping[Config['key_add_feed']]:
            self._display.add_feed()
        elif c == key_mapping[Config['key_delete']]:
            if self._active_window == 0:
                self._display.delete_feed(self._feed_menu.item())
                self._feed_menu.update_child()
        elif c == key_mapping[Config['key_reload']]:
            self._display.reload_feeds()
        elif c == key_mapping[Config['key_save']]:
            if self._active_window == 0 and self._feed_menu.item():
                self._display.save_episodes(feed=self._feed_menu.item())
            elif self._active_window == 1 and self._episode_menu.item():
                self._display.save_episodes(episode=self._episode_menu.item())
        elif c == key_mapping[Config['key_invert']]:
            self._invert_selected_menu()
        elif c == key_mapping[Config['key_mark_played']]:
            if self._active_window == 0:
                feed = self._feed_menu.item()
                if feed is not None:
                    episodes = self._display.database.episodes(feed)
                    for episode in episodes:
                        episode.played = not episode.played
                    self._display.modified_episodes.extend(episodes)
            elif self._active_window == 1:
                episode = self._episode_menu.item()
                if episode is not None:
                    episode.played = not episode.played
                    self._display.modified_episodes.append(episode)
                    self._episode_menu.move(-1)

        return keep_running

    def made_active(self) -> None:
        """Called each time the perspective is made active (switched to).

        Overrides method from Perspective; see documentation in that class.
        """

    def update_menus(self) -> None:
        """Update/refresh the contents of all menus.

        Overrides method from Perspective; see documentation in that class.
        """
        self._feed_menu.update_items(None)
        self._feed_menu.update_child()

    def refresh(self) -> None:
        """Refresh the screen and all windows.

        Overrides method from Perspective; see documentation in that class.
        """
        self._feed_window.refresh()
        self._episode_window.refresh()
        self._metadata_window.refresh()

    def _get_active_menu(self) -> Menu:
        """Retrieve the active Menu, if there is one.

        Overrides method from Perspective; see documentation in that class.
        """
        assert 0 <= self._active_window < 2

        return {
            0: self._feed_menu,
            1: self._episode_menu,
        }.get(self._active_window)

    def _create_player_from_selected(self) -> None:
        """Creates player(s) based on the selected items and adds to the queue.

        If the active menu is the feed menu, then this will create players for
        all episodes in the selected feed. If the active menu is the episode
        menu, this will simply create a single player.

        This method will not clear the queue prior to adding the new player(s),
        nor will it play the episodes after running.
        """
        if self._active_window == 0:
            feed = self._feed_menu.item()
            if feed is not None:
                for episode in self._display.database.episodes(feed):
                    player = Player.create_instance(
                        self._display.AVAILABLE_PLAYERS, str(episode),
                        episode.get_playable(), episode)
                    self._display.queue.add(player)
        elif self._active_window == 1:
            episode = self._episode_menu.item()
            if episode is not None:
                player = Player.create_instance(
                    self._display.AVAILABLE_PLAYERS, str(episode),
                    episode.get_playable(), episode)
                self._display.queue.add(player)

    def _invert_selected_menu(self) -> None:
        """Inverts the contents of the selected menu.
        """
        self._get_active_menu().invert()
        if self._feed_menu:
            self._feed_menu.update_child()
Beispiel #9
0
def test_menu_episode_invert():
    mymenu = EpisodeMenu(window, source)
    mymenu.invert()
    assert mymenu._inverted
    mymenu.update_items(feed)
Beispiel #10
0
def test_menu_episode_update_child(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    items = mymenu._items
    mymenu.update_child()
    assert mymenu._items == items
Beispiel #11
0
def test_menu_episode_metadata():
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    assert mymenu.metadata == episode1.metadata
Beispiel #12
0
def test_menu_episode_metadata_none():
    mymenu = EpisodeMenu(window, source)
    assert mymenu.metadata == ""
    mymenu.update_items(None)
    assert mymenu.metadata == ""
Beispiel #13
0
def test_menu_episode_item():
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    assert mymenu.item == episode1
    mymenu._selected += 1
    assert mymenu.item == episode2
Beispiel #14
0
def test_menu_episode_item_none():
    mymenu = EpisodeMenu(window, source)
    assert mymenu.item is None
Beispiel #15
0
def test_menu_episode_update_items(mock_color_pair, mock_A_NORMAL):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    source.episodes.assert_called_with(feed)
    assert len(mymenu._items()) == 2
    assert len(mymenu) == 2
Beispiel #16
0
def test_menu_episode_init():
    mymenu = EpisodeMenu(window, source)
    assert isinstance(mymenu, EpisodeMenu)
Beispiel #17
0
def test_menu_episode_invert(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    mymenu.invert()
    assert mymenu._inverted
    mymenu.update_items(feed)
Beispiel #18
0
def test_menu_episode_metadata(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    assert mymenu.metadata() == episode1.metadata
Beispiel #19
0
def test_menu_episode_metadata_none(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    assert mymenu.metadata() == ""
    mymenu.update_items(None)
    assert mymenu.metadata() == ""
Beispiel #20
0
def test_menu_episode_item(mock_A_NORMAL, mock_color_pair):
    mymenu = EpisodeMenu(window, source)
    mymenu.update_items(feed)
    assert mymenu.item() == episode1
    mymenu._selected += 1
    assert mymenu.item() == episode2