Exemplo n.º 1
0
class TableContainer(QFrame, BgTransparentMixin):
    def __init__(self, app, parent=None):
        super().__init__(parent)

        self._app = app
        self._renderer = None
        self._table = None  # current visible table
        self._tables = []

        self._extra = None
        self.toolbar = SongsTableToolbar()
        self.tabbar = TableTabBarV2()
        self.meta_widget = TableMetaWidget(parent=self)
        self.songs_table = SongsTableView(parent=self)
        self.albums_table = AlbumListView(parent=self)
        self.artists_table = ArtistListView(parent=self)
        self.videos_table = VideoListView(parent=self)
        self.playlists_table = PlaylistListView(parent=self)
        self.desc_widget = DescLabel(parent=self)

        self._tables.append(self.songs_table)
        self._tables.append(self.albums_table)
        self._tables.append(self.artists_table)
        self._tables.append(self.playlists_table)
        self._tables.append(self.videos_table)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.videos_table.play_video_needed.connect(
            lambda video: aio.create_task(self.play_video(video)))

        def goto_model(model):
            self._app.browser.goto(model=model)

        for signal in [
                self.songs_table.show_artist_needed,
                self.songs_table.show_album_needed,
                self.albums_table.show_album_needed,
                self.artists_table.show_artist_needed,
                self.playlists_table.show_playlist_needed,
        ]:
            signal.connect(goto_model)

        self.toolbar.play_all_needed.connect(self.play_all)
        self.songs_table.add_to_playlist_needed.connect(
            self._add_songs_to_playlist)

        self._setup_ui()

    def _setup_ui(self):
        self.current_table = None
        self.tabbar.hide()
        self.meta_widget.add_tabbar(self.tabbar)
        self.desc_widget.hide()

        self._layout = QVBoxLayout(self)
        self._layout.addWidget(self.meta_widget)
        self._layout.addWidget(self.toolbar)
        self._layout.addSpacing(10)
        self._layout.addWidget(self.desc_widget)
        for table in self._tables:
            self._layout.addWidget(table)
        self._layout.addStretch(0)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)

    @property
    def current_extra(self):
        return self._extra

    @current_extra.setter
    def current_extra(self, extra):
        """(alpha)"""
        if self._extra is not None:
            self._layout.removeWidget(self._extra)
            self._extra.deleteLater()
            del self._extra
        self._extra = extra
        if self._extra is not None:
            self._layout.insertWidget(1, self._extra)

    @property
    def current_table(self):
        """current visible table, if no table is visible, return None"""
        return self._table

    @current_table.setter
    def current_table(self, table):
        """set table as current visible table

        show table and hide other tables, if table is None,
        hide all tables.
        """
        for t in self._tables:
            if t != table:
                t.hide()
        if table is None:
            self.toolbar.hide()
        else:
            self.desc_widget.hide()
            table.show()
            if table is self.artists_table:
                self.toolbar.artists_mode()
            if table is self.albums_table:
                self.toolbar.albums_mode()
            if table is self.songs_table:
                self.toolbar.songs_mode()
        self._table = table

    async def set_renderer(self, renderer):
        """set ui renderer

        TODO: add lock for set_renderer
        """

        if renderer is None:
            return

        # firstly, tear down everything
        # tear down last renderer
        if self._renderer is not None:
            await self._renderer.tearDown()
        self.meta_widget.hide()
        self.meta_widget.clear()
        self.tabbar.hide()
        self.tabbar.check_default()
        self.current_table = None
        self.current_extra = None
        # clean right_panel background image
        self._app.ui.right_panel.show_background_image(None)
        # disconnect songs_table signal
        signals = (
            self.tabbar.show_contributed_albums_needed,
            self.tabbar.show_albums_needed,
            self.tabbar.show_songs_needed,
            self.tabbar.show_artists_needed,
            self.tabbar.show_playlists_needed,
            self.tabbar.show_desc_needed,
        )
        for signal in signals:
            disconnect_slots_if_has(signal)

        # unbind some callback function
        self.songs_table.remove_song_func = None

        # secondly, prepare environment
        self.show()

        # thirdly, setup new renderer
        await renderer.setUp(self)
        self._renderer = renderer
        await self._renderer.render()

    async def play_song(self, song):
        self._app.player.play_song(song)

    async def play_video(self, video):
        media = await aio.run_in_executor(None, lambda: video.media)
        self._app.player.play(media)

    def play_all(self):
        task_name = 'play-all'
        task_spec = self._app.task_mgr.get_or_create(task_name)

        def reader_readall_cb(task):
            with suppress(ProviderIOError, asyncio.CancelledError):
                songs = task.result()
                self._app.player.play_songs(songs=songs)
            self.toolbar.enter_state_playall_end()

        model = self.songs_table.model()
        # FIXME: think about a more elegant way
        reader = model.sourceModel().songs_g
        if reader is not None:
            if reader.count is not None:
                task = task_spec.bind_blocking_io(reader.readall)
                self.toolbar.enter_state_playall_start()
                task.add_done_callback(reader_readall_cb)
                return
        songs = model.sourceModel().songs
        self._app.player.play_songs(songs=songs)

    async def show_model(self, model):
        model_type = ModelType(model.meta.model_type)
        if model_type == ModelType.album:
            renderer = AlbumRenderer(model)
        elif model_type == ModelType.artist:
            renderer = ArtistRenderer(model)
        elif model_type == ModelType.playlist:
            renderer = PlaylistRenderer(model)
        else:
            renderer = None
        await self.set_renderer(renderer)

    def show_collection(self, coll):
        renderer = SongsCollectionRenderer(coll)
        aio.create_task(self.set_renderer(renderer))

    def show_songs(self, songs=None, songs_g=None):
        """(DEPRECATED) provided only for backward compatibility"""
        renderer = Renderer()
        task = aio.create_task(self.set_renderer(renderer))
        task.add_done_callback(
            lambda _: renderer.show_songs(songs=songs, songs_g=songs_g))

    def show_albums_coll(self, albums_g):
        aio.create_task(self.set_renderer(AlbumsCollectionRenderer(albums_g)))

    def show_artists_coll(self, artists_g):
        aio.create_task(self.set_renderer(
            ArtistsCollectionRenderer(artists_g)))

    def show_player_playlist(self):
        aio.create_task(self.set_renderer(PlayerPlaylistRenderer()))

    def search(self, text):
        if self.isVisible() and self.songs_table is not None:
            self.songs_table.filter_row(text)

    def _add_songs_to_playlist(self, songs):
        for song in songs:
            self._app.playlist.add(song)
Exemplo n.º 2
0
class TableContainer(QFrame, BgTransparentMixin):
    def __init__(self, app, parent=None):
        super().__init__(parent)

        self._app = app
        self._renderer = None
        self._table = None  # current visible table
        self._tables = []

        self._extra = None
        self.toolbar = SongsTableToolbar()
        self.tabbar = TableTabBarV2()
        self.meta_widget = TableMetaWidget(parent=self)
        self.songs_table = SongsTableView(parent=self)
        self.albums_table = AlbumListView(parent=self)
        self.artists_table = ArtistListView(parent=self)
        self.videos_table = VideoListView(parent=self)
        self.playlists_table = PlaylistListView(parent=self)
        self.comments_table = CommentListView(parent=self)
        self.desc_widget = DescLabel(parent=self)

        self._tables.append(self.songs_table)
        self._tables.append(self.albums_table)
        self._tables.append(self.artists_table)
        self._tables.append(self.playlists_table)
        self._tables.append(self.videos_table)
        self._tables.append(self.comments_table)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.videos_table.play_video_needed.connect(
            lambda video: aio.create_task(self.play_video(video)))

        def goto_model(model):
            self._app.browser.goto(model=model)

        for signal in [
                self.songs_table.show_artist_needed,
                self.songs_table.show_album_needed,
                self.albums_table.show_album_needed,
                self.artists_table.show_artist_needed,
                self.playlists_table.show_playlist_needed,
        ]:
            signal.connect(goto_model)

        self.toolbar.play_all_needed.connect(self.play_all)
        self.songs_table.add_to_playlist_needed.connect(
            self._add_songs_to_playlist)
        self.songs_table.about_to_show_menu.connect(
            self._songs_table_about_to_show_menu)
        self.songs_table.activated.connect(lambda index: aio.create_task(
            self._on_songs_table_activated(index)))

        self._setup_ui()

    def _setup_ui(self):
        self.current_table = None
        self.tabbar.hide()
        self.meta_widget.add_tabbar(self.tabbar)
        self.desc_widget.hide()

        self._layout = QVBoxLayout(self)
        self._layout.addWidget(self.meta_widget)
        self._layout.addWidget(self.toolbar)
        self._layout.addSpacing(10)
        self._layout.addWidget(self.desc_widget)
        for table in self._tables:
            self._layout.addWidget(table)
        self._layout.addStretch(0)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)

    @property
    def current_extra(self):
        return self._extra

    @current_extra.setter
    def current_extra(self, extra):
        """(alpha)"""
        if self._extra is not None:
            self._layout.removeWidget(self._extra)
            self._extra.deleteLater()
            del self._extra
        self._extra = extra
        if self._extra is not None:
            self._layout.insertWidget(1, self._extra)

    @property
    def current_table(self):
        """current visible table, if no table is visible, return None"""
        return self._table

    @current_table.setter
    def current_table(self, table):
        """set table as current visible table

        show table and hide other tables, if table is None,
        hide all tables.
        """
        for t in self._tables:
            if t != table:
                t.hide()
        if table is None:
            self.toolbar.hide()
        else:
            self.desc_widget.hide()
            table.show()
            if table is self.artists_table:
                self.toolbar.artists_mode()
            if table is self.albums_table:
                self.toolbar.albums_mode()
            if table is self.songs_table:
                self.toolbar.songs_mode()
        self._table = table

    async def set_renderer(self, renderer):
        """set ui renderer

        TODO: add lock for set_renderer
        """

        if renderer is None:
            return

        # firstly, tear down everything
        # tear down last renderer
        if self._renderer is not None:
            await self._renderer.tearDown()
        self.meta_widget.hide()
        self.meta_widget.clear()
        self.tabbar.hide()
        self.tabbar.check_default()
        self.current_table = None
        self.current_extra = None
        # clean right_panel background image
        self._app.ui.right_panel.show_background_image(None)
        # disconnect songs_table signal
        signals = (
            self.tabbar.show_contributed_albums_needed,
            self.tabbar.show_albums_needed,
            self.tabbar.show_songs_needed,
            self.tabbar.show_artists_needed,
            self.tabbar.show_playlists_needed,
            self.tabbar.show_desc_needed,
        )
        for signal in signals:
            disconnect_slots_if_has(signal)

        # unbind some callback function
        self.songs_table.remove_song_func = None

        # secondly, prepare environment
        self.show()

        # thirdly, setup new renderer
        await renderer.setUp(self)
        self._renderer = renderer
        await self._renderer.render()

    async def play_song(self, song):
        self._app.player.play_song(song)

    async def play_video(self, video):
        media = await aio.run_in_executor(None, lambda: video.media)
        self._app.player.play(media)

    def play_all(self):
        task_name = 'play-all'
        task_spec = self._app.task_mgr.get_or_create(task_name)

        def reader_readall_cb(task):
            with suppress(ProviderIOError, asyncio.CancelledError):
                songs = task.result()
                self._app.player.play_songs(songs=songs)
            self.toolbar.enter_state_playall_end()

        model = self.songs_table.model()
        # FIXME: think about a more elegant way
        reader = model.sourceModel()._reader
        if reader is not None:
            if reader.count is not None:
                task = task_spec.bind_blocking_io(reader.readall)
                self.toolbar.enter_state_playall_start()
                task.add_done_callback(reader_readall_cb)
                return
        songs = model.sourceModel().songs
        self._app.player.play_songs(songs=songs)

    async def show_model(self, model):
        model = self._app.library.cast_model_to_v1(model)
        model_type = ModelType(model.meta.model_type)
        if model_type == ModelType.album:
            renderer = AlbumRenderer(model)
        elif model_type == ModelType.artist:
            renderer = ArtistRenderer(model)
        elif model_type == ModelType.playlist:
            renderer = PlaylistRenderer(model)
        else:
            renderer = None
        await self.set_renderer(renderer)

    def show_collection(self, coll):
        renderer = SongsCollectionRenderer(coll)
        aio.create_task(self.set_renderer(renderer))

    def show_songs(self, songs=None, songs_g=None):
        """(DEPRECATED) provided only for backward compatibility"""
        warnings.warn('use readerer.show_songs please')
        renderer = Renderer()
        task = aio.create_task(self.set_renderer(renderer))
        if songs is not None:
            reader = wrap(songs)
        else:
            reader = songs_g
        task.add_done_callback(lambda _: renderer.show_songs(reader=reader))

    def show_albums_coll(self, albums_g):
        aio.create_task(self.set_renderer(AlbumsCollectionRenderer(albums_g)))

    def show_artists_coll(self, artists_g):
        aio.create_task(self.set_renderer(
            ArtistsCollectionRenderer(artists_g)))

    def show_player_playlist(self):
        aio.create_task(self.set_renderer(PlayerPlaylistRenderer()))

    def search(self, text):
        if self.isVisible() and self.songs_table is not None:
            self.songs_table.filter_row(text)

    def _add_songs_to_playlist(self, songs):
        for song in songs:
            self._app.playlist.add(song)

    def _songs_table_about_to_show_menu(self, ctx):
        add_action = ctx['add_action']
        models = ctx['models']
        if not models or models[0].meta.model_type != ModelType.song:
            return

        song = models[0]
        goto = self._app.browser.goto

        if self._app.library.check_flags_by_model(song, ProviderFlags.similar):
            add_action('相似歌曲', lambda *args: goto(model=song, path='/similar'))
        if self._app.library.check_flags_by_model(song,
                                                  ProviderFlags.hot_comments):
            add_action('歌曲评论',
                       lambda *args: goto(model=song, path='/hot_comments'))

    async def _on_songs_table_activated(self, index):
        """
        QTableView should have no IO operations.
        """
        from feeluown.widgets.songs import Column

        song = index.data(Qt.UserRole)
        if index.column() == Column.song:
            self.songs_table.play_song_needed.emit(song)
        else:
            try:
                song = await aio.run_in_executor(
                    None, self._app.library.song_upgrade, song)
            except NotSupported:
                assert ModelFlags.v2 & song.meta.flags
                self._app.show_msg('资源提供放不支持该功能')
                logger.info(
                    f'provider:{song.source} does not support song_get')
                song.state = ModelState.cant_upgrade
            except (ProviderIOError, RequestException) as e:
                # FIXME: we should only catch ProviderIOError here,
                # but currently, some plugins such fuo-qqmusic may raise
                # requests.RequestException
                logger.exception('upgrade song failed')
                self._app.show_msg(f'请求失败: {str(e)}')
            else:
                if index.column() == Column.artist:
                    artists = song.artists
                    if artists:
                        if len(artists) > 1:
                            self.songs_table.show_artists_by_index(index)
                        else:
                            self.songs_table.show_artist_needed.emit(
                                artists[0])
                elif index.column() == Column.album:
                    self.songs_table.show_album_needed.emit(song.album)
        model = self.songs_table.model()
        topleft = model.index(index.row(), 0)
        bottomright = model.index(index.row(), 4)
        model.dataChanged.emit(topleft, bottomright, [])