示例#1
0
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.songs_table = SongsTableView(self)
        self._toolbar = TableToolbar(self)
        self._cover_label = QLabel(self)
        self._desc_container_folded = True
        self._desc_container = DescriptionContainer(self)
        self._top_container = QWidget(self)
        self._cover_container = QWidget(self._top_container)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.songs_table.show_artist_needed.connect(
            lambda artist: self._app.browser.goto(model=artist))
        self.songs_table.show_album_needed.connect(
            lambda album: self._app.browser.goto(model=album))

        self._desc_container.space_pressed.connect(
            self.toggle_desc_container_fold)
        self._toolbar.toggle_desc_needed.connect(
            self.toggle_desc_container_fold)
        self._toolbar.play_all_needed.connect(self.play_all)

        self.hide()
        self._setup_ui()
示例#2
0
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.meta_widget = SongsTableMetaWidget(parent=self)
        self.songs_table = SongsTableView(parent=self)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.songs_table.show_artist_needed.connect(
            lambda artist: self._app.browser.goto(model=artist))
        self.songs_table.show_album_needed.connect(
            lambda album: self._app.browser.goto(model=album))

        self.meta_widget.toolbar.play_all_needed.connect(self.play_all)
        self.meta_widget.toggle_full_window_needed.connect(self.toggle_meta_full_window)

        self.hide()
        self._setup_ui()
示例#3
0
class SongsTableContainer(QFrame):
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.songs_table = SongsTableView(self)
        self._toolbar = TableToolbar(self)
        self._cover_label = QLabel(self)
        self._desc_container_folded = True
        self._desc_container = DescriptionContainer(self)
        self._top_container = QWidget(self)
        self._cover_container = QWidget(self._top_container)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.songs_table.show_artist_needed.connect(
            lambda artist: self._app.browser.goto(model=artist))
        self.songs_table.show_album_needed.connect(
            lambda album: self._app.browser.goto(model=album))

        self._desc_container.space_pressed.connect(
            self.toggle_desc_container_fold)
        self._toolbar.toggle_desc_needed.connect(
            self.toggle_desc_container_fold)
        self._toolbar.play_all_needed.connect(self.play_all)

        self.hide()
        self._setup_ui()

    def _setup_ui(self):
        self._left_sub_layout = QVBoxLayout(self._cover_container)
        self._top_layout = QHBoxLayout(self._top_container)
        self._layout = QVBoxLayout(self)

        self._cover_label.setMinimumWidth(200)
        self._right_sub_layout = QVBoxLayout()
        self._right_sub_layout.addWidget(self._desc_container)
        self._right_sub_layout.addWidget(self._toolbar)
        self._left_sub_layout.addWidget(self._cover_label)
        self._left_sub_layout.addStretch(0)
        # 根据 Qt 文档中所说,在大部分平台中,ContentMargin 为 11
        self._left_sub_layout.setContentsMargins(0, 0, 11, 0)
        self._left_sub_layout.setSpacing(0)

        self._top_layout.addWidget(self._cover_container)
        self._top_layout.addLayout(self._right_sub_layout)
        self._top_layout.setStretch(1, 1)

        self.setAutoFillBackground(False)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)
        self._layout.addWidget(self._top_container)
        self._layout.addWidget(self.songs_table)

        self._top_container.setSizePolicy(QSizePolicy.Expanding,
                                          QSizePolicy.Maximum)

        # FIXME: 更好的计算宽度和高度
        # 目前是假设知道自己初始化高度大约是 530px
        # 之后可以考虑按比例来计算
        self.overview_height = 180
        self._top_container.setMaximumHeight(self.overview_height)
        self._songs_table_height = 530 - self.overview_height
        self.songs_table.setMinimumHeight(self._songs_table_height)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    async def play_song(self, song):
        await async_run(lambda: song.url)
        self._app.player.play_song(song)

    def play_all(self):
        songs = self.songs_table.model().songs
        self._app.player.playlist.clear()
        for song in songs:
            self._app.player.playlist.add(song)
        self._app.player.play_next()

    async def show_model(self, model):
        model_type = ModelType(model._meta.model_type)
        if model_type == ModelType.album:
            func = self.show_album
        elif model_type == ModelType.artist:
            func = self.show_artist
        elif model_type == ModelType.playlist:
            func = self.show_playlist
        else:

            def func(model):
                pass  # seems silly

        await func(model)

    def show_player_playlist(self, songs):
        self.show_songs(songs)
        self.songs_table.song_deleted.connect(
            lambda song: self._app.playlist.remove(song))

    def set_desc(self, desc):
        self._desc_container.show()
        self._desc_container.set_html(desc)

    async def show_playlist(self, playlist):
        self._top_container.show()
        loop = asyncio.get_event_loop()
        if playlist.meta.allow_create_songs_g:
            songs_g = playlist.create_songs_g()
            self._show_songs(songs_g=songs_g)
        else:
            songs = await async_run(lambda: playlist.songs, loop=loop)
            self._show_songs(songs)
        desc = '<h2>{}</h2>\n{}'.format(playlist.name, playlist.desc or '')
        self.set_desc(desc)
        if playlist.cover:
            loop.create_task(self.show_cover(playlist.cover))

        def remove_song(song):
            model = self.songs_table.model()
            row = model.songs.index(song)
            msg = 'remove {} from {}'.format(song, playlist)
            with self._app.create_action(msg) as action:
                rv = playlist.remove(song.identifier)
                if rv:
                    model.removeRow(row)
                else:
                    action.failed()

        self.songs_table.song_deleted.connect(lambda song: remove_song(song))

    async def show_artist(self, artist):
        self._top_container.show()
        loop = asyncio.get_event_loop()
        songs = songs_g = None
        if artist.meta.allow_create_songs_g:
            songs_g = artist.create_songs_g()
        else:
            songs = await async_run(lambda: artist.songs)
        if songs_g is not None:
            self._show_songs(songs_g=songs_g)
        else:
            self._show_songs(songs=songs)
        desc = await async_run(lambda: artist.desc)
        self.set_desc(desc or '<h2>{}</h2>'.format(artist.name))
        if artist.cover:
            loop.create_task(self.show_cover(artist.cover))

    async def show_album(self, album):
        self._top_container.show()
        loop = asyncio.get_event_loop()
        songs = await async_run(lambda: album.songs)
        self._show_songs(songs)
        desc = await async_run(lambda: album.desc)
        self.set_desc(desc or '<h2>{}</h2>'.format(album.name))
        if album.cover:
            loop.create_task(self.show_cover(album.cover))

    def show_collection(self, collection):
        self._top_container.hide()
        self.show_songs(collection.models)
        self.songs_table.song_deleted.connect(collection.remove)

    async def show_url(self, url):
        model = self._app.protocol.get_model(url)
        if model.meta.model_type == ModelType.song:
            self._app.player.play_song(model)
        else:
            # TODO: add artist/album/user support
            self._app.show_msg('暂时只支持歌曲,不支持其它歌曲资源')

    async def show_cover(self, cover):
        # FIXME: cover_hash may not work properly someday
        cover_uid = cover.split('/', -1)[-1]
        content = await self._app.img_mgr.get(cover, cover_uid)
        img = QImage()
        img.loadFromData(content)
        pixmap = QPixmap(img)
        if not pixmap.isNull():
            self.set_cover(pixmap)
            self.update()

    def _show_songs(self, songs=None, songs_g=None):
        try:
            self.songs_table.song_deleted.disconnect()
        except TypeError:  # no connections at all
            pass
        self.show()
        self.songs_table.show()
        songs = songs or []
        logger.debug('Show songs in table, total: %d', len(songs))
        source_name_map = {
            p.identifier: p.name
            for p in self._app.library.list()
        }
        if songs_g is not None:  # 优先使用生成器
            self.songs_table.setModel(
                SongsTableModel(source_name_map=source_name_map,
                                songs_g=songs_g,
                                parent=self.songs_table))
        else:
            self.songs_table.setModel(
                SongsTableModel(songs=songs,
                                source_name_map=source_name_map,
                                parent=self.songs_table))
        self.songs_table.scrollToTop()

    def show_songs(self, songs):
        self._show_songs(songs)
        self._top_container.show()
        self.hide_desc()
        self.hide_cover()

    def set_cover(self, pixmap):
        self._cover_container.show()
        self._cover_label.setPixmap(
            pixmap.scaledToWidth(self._cover_label.width(),
                                 mode=Qt.SmoothTransformation))

    def toggle_desc_container_fold(self):
        # TODO: add toggle animation?
        if self._desc_container_folded:
            self._top_container.setMaximumHeight(4000)
            self.songs_table.hide()
            self._desc_container_folded = False
        else:
            self._top_container.setMaximumHeight(self.overview_height)
            self.songs_table.show()
            self._desc_container_folded = True

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

    def hide_cover(self):
        self._cover_container.hide()

    def hide_desc(self):
        self._desc_container.hide()
示例#4
0
class TableContainer(QFrame):
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.toolbar = SongsTableToolbar()
        self.meta_widget = TableMetaWidget(self.toolbar, parent=self)
        self.songs_table = SongsTableView(parent=self)
        self.albums_table = AlbumListView(parent=self)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.songs_table.show_artist_needed.connect(
            lambda artist: self._app.browser.goto(model=artist))
        self.songs_table.show_album_needed.connect(
            lambda album: self._app.browser.goto(model=album))

        self.meta_widget.toolbar.play_all_needed.connect(self.play_all)
        self.meta_widget.toggle_full_window_needed.connect(
            self.toggle_meta_full_window)

        self.hide()
        self._setup_ui()

        self._delegate = None

    def _setup_ui(self):
        self.setAutoFillBackground(False)

        self._layout = QVBoxLayout(self)
        self._layout.addWidget(self.meta_widget)
        self._layout.addWidget(self.songs_table)
        self._layout.addWidget(self.albums_table)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)

    async def set_delegate(self, delegate):
        """set ui delegate

        TODO: add lock for set_delegate
        """

        if delegate is None:
            return

        # firstly, tear down everything
        # tear down last delegate
        if self._delegate is not None:
            await self._delegate.tearDown()
        self.meta_widget.clear()
        self.songs_table.hide()
        self.albums_table.hide()
        # disconnect songs_table signal
        signals = (
            self.songs_table.song_deleted,
            self.meta_widget.toolbar.show_albums_needed,
            self.meta_widget.toolbar.show_songs_needed,
            self.albums_table.show_album_needed,
        )
        for signal in signals:
            try:
                signal.disconnect()
            except TypeError:
                pass

        # secondly, prepare environment
        self.show()

        # thirdly, setup new delegate
        await delegate.setUp(self)
        self._delegate = delegate
        await self._delegate.render()

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

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

        def songs_g_readall_cb(task):
            try:
                songs = task.result()
            except asyncio.CancelledError:
                pass
            except ProviderIOError as e:
                self._app.show_msg('[play-all] read songs failed: {}'.format(
                    str(e)))
            else:
                self._app.player.play_songs(songs=songs)
            finally:
                self.meta_widget.toolbar.enter_state_playall_end()

        model = self.songs_table.model()
        songs_g = model.songs_g
        if songs_g is not None and songs_g.allow_random_read:
            task = task_spec.bind_blocking_io(songs_g.readall)
            self.meta_widget.toolbar.enter_state_playall_start()
            task.add_done_callback(songs_g_readall_cb)
            return
        songs = model.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:
            delegate = AlbumDelegate(model)
        elif model_type == ModelType.artist:
            delegate = ArtistDelegate(model)
        elif model_type == ModelType.playlist:
            delegate = PlaylistDelegate(model)
        else:
            delegate = None
        await self.set_delegate(delegate)

    def show_collection(self, coll):
        delegate = CollectionDelegate(coll)
        aio.create_task(self.set_delegate(delegate))

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

    def show_albums(self, albums_g):
        delegate = Delegate()
        task = aio.create_task(self.set_delegate(delegate))
        task.add_done_callback(lambda _: delegate.show_albums(albums_g))

    def show_player_playlist(self):
        aio.create_task(self.set_delegate(PlayerPlaylistDelegate()))

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

    def toggle_meta_full_window(self, fullwindow_needed):
        if fullwindow_needed:
            self.songs_table.hide()
        else:
            self.songs_table.show()
示例#5
0
class SongsTableContainer(QFrame):
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.meta_widget = TableMetaWidget(parent=self)
        self.songs_table = SongsTableView(parent=self)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.songs_table.show_artist_needed.connect(
            lambda artist: self._app.browser.goto(model=artist))
        self.songs_table.show_album_needed.connect(
            lambda album: self._app.browser.goto(model=album))

        self.meta_widget.toolbar.play_all_needed.connect(self.play_all)
        self.meta_widget.toggle_full_window_needed.connect(
            self.toggle_meta_full_window)

        self.hide()
        self._setup_ui()

    def _setup_ui(self):
        self.setAutoFillBackground(False)

        self._layout = QVBoxLayout(self)
        self._layout.addWidget(self.meta_widget)
        self._layout.addWidget(self.songs_table)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)

    async def play_song(self, song):
        await async_run(lambda: song.url)
        self._app.player.play_song(song)

    def play_all(self):
        songs = self.songs_table.model().songs
        self._app.player.playlist.clear()
        for song in songs:
            self._app.player.playlist.add(song)
        self._app.player.play_next()

    async def show_model(self, model):
        model_type = ModelType(model._meta.model_type)
        if model_type == ModelType.album:
            func = self.show_album
        elif model_type == ModelType.artist:
            func = self.show_artist
        elif model_type == ModelType.playlist:
            func = self.show_playlist
        else:

            def func(model):
                pass  # seems silly

        await func(model)

    def show_player_playlist(self, songs):
        self.show_songs(songs)
        self.songs_table.song_deleted.connect(
            lambda song: self._app.playlist.remove(song))

    async def show_playlist(self, playlist):
        loop = asyncio.get_event_loop()
        if playlist.meta.allow_create_songs_g:
            songs_g = playlist.create_songs_g()
            self._show_songs(songs_g=songs_g)
        else:
            songs = await async_run(lambda: playlist.songs, loop=loop)
            self._show_songs(songs)
        self.meta_widget.clear()
        self.meta_widget.title = playlist.name
        desc = await async_run(lambda: playlist.desc)
        self.meta_widget.desc = desc
        if playlist.cover:
            loop.create_task(self.show_cover(playlist.cover))

        def remove_song(song):
            model = self.songs_table.model()
            row = model.songs.index(song)
            msg = 'remove {} from {}'.format(song, playlist)
            with self._app.create_action(msg) as action:
                rv = playlist.remove(song.identifier)
                if rv:
                    model.removeRow(row)
                else:
                    action.failed()

        self.songs_table.song_deleted.connect(lambda song: remove_song(song))

    async def show_artist(self, artist):
        loop = asyncio.get_event_loop()
        songs = songs_g = None
        if artist.meta.allow_create_songs_g:
            songs_g = artist.create_songs_g()
        else:
            songs = await async_run(lambda: artist.songs)
        if songs_g is not None:
            self._show_songs(songs_g=songs_g)
        else:
            self._show_songs(songs=songs)
        desc = await async_run(lambda: artist.desc)
        self.meta_widget.clear()
        self.meta_widget.title = artist.name
        self.meta_widget.desc = desc
        if artist.cover:
            loop.create_task(self.show_cover(artist.cover))

    async def show_album(self, album):
        loop = asyncio.get_event_loop()
        songs = await async_run(lambda: album.songs)
        self.meta_widget.clear()
        self._show_songs(songs)
        desc = await async_run(lambda: album.desc)
        self.meta_widget.title = album.name
        self.meta_widget.desc = desc
        if album.cover:
            loop.create_task(self.show_cover(album.cover))

    def show_collection(self, collection):
        self.meta_widget.clear()
        self.meta_widget.title = collection.name
        self.meta_widget.updated_at = collection.updated_at
        self.meta_widget.created_at = collection.created_at
        self._show_songs(collection.models)
        self.songs_table.song_deleted.connect(collection.remove)

    async def show_url(self, url):
        model = self._app.protocol.get_model(url)
        if model.meta.model_type == ModelType.song:
            self._app.player.play_song(model)
        else:
            # TODO: add artist/album/user support
            self._app.show_msg('暂时只支持歌曲,不支持其它歌曲资源')

    async def show_cover(self, cover):
        # FIXME: cover_hash may not work properly someday
        cover_uid = cover.split('/', -1)[-1]
        content = await self._app.img_mgr.get(cover, cover_uid)
        img = QImage()
        img.loadFromData(content)
        pixmap = QPixmap(img)
        if not pixmap.isNull():
            self.set_cover(pixmap)
            self.update()

    def _show_songs(self, songs=None, songs_g=None):
        try:
            self.songs_table.song_deleted.disconnect()
        except TypeError:  # no connections at all
            pass
        self.show()
        self.songs_table.show()
        songs = songs or []
        logger.debug('Show songs in table, total: %d', len(songs))
        source_name_map = {
            p.identifier: p.name
            for p in self._app.library.list()
        }
        if songs_g is not None:  # 优先使用生成器
            self.songs_table.setModel(
                SongsTableModel(source_name_map=source_name_map,
                                songs_g=songs_g,
                                parent=self.songs_table))
        else:
            self.songs_table.setModel(
                SongsTableModel(songs=songs,
                                source_name_map=source_name_map,
                                parent=self.songs_table))
        self.songs_table.scrollToTop()

    def show_songs(self, songs=None, songs_g=None):
        self.meta_widget.clear()
        self._show_songs(songs=songs, songs_g=songs_g)

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

    def set_cover(self, pixmap):
        self.meta_widget.set_cover_pixmap(pixmap)

    def toggle_meta_full_window(self, fullwindow_needed):
        if fullwindow_needed:
            self.songs_table.hide()
        else:
            self.songs_table.show()