Esempio n. 1
0
class SongsTableContainer(QFrame):
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.songs_table = SongsTableView(self)
        self.table_overview = TableOverview(self)

        self._layout = QVBoxLayout(self)

        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)
        self._layout.addWidget(self.table_overview)
        self._layout.addSpacing(10)
        self._layout.addWidget(self.songs_table)
        self._layout.addSpacing(10)

        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: asyncio.ensure_future(self.show_model(artist)))
        self.songs_table.show_album_needed.connect(
            lambda album: asyncio.ensure_future(self.show_model(album)))
        self.hide()

    async def play_song(self, song):
        loop = asyncio.get_event_loop()
        with self._app.action_log('play {}'.format(song)):
            await loop.run_in_executor(None, 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 = 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

        self._app.histories.append(model)
        with self._app.action_log('show {}'.format(str(model))):
            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):
        self.table_overview.show()
        loop = asyncio.get_event_loop()
        songs = await loop.run_in_executor(None, lambda: playlist.songs)
        self._show_songs(songs)
        self.table_overview.set_name(playlist.name)
        self.table_overview.set_desc(playlist.desc or '')
        if playlist.cover:
            loop.create_task(self.show_cover(playlist.cover))

        def remove_song(song):
            rv = playlist.remove(song.identifier)
            if rv:
                self.songs_table.model().remove_song(song)
                self._app.ui.magicbox.show_msg(
                    'remove {}...success'.format(song))
            else:
                self._app.ui.magicbox.show_msg(
                    'remove {}...failed'.format(song))

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

    async def show_artist(self, artist):
        self.table_overview.show()
        loop = asyncio.get_event_loop()
        songs = await loop.run_in_executor(None, lambda: artist.songs)
        self.table_overview.set_desc(artist.desc or '')
        self.table_overview.set_name(artist.name)
        self._show_songs(songs)
        if artist.cover:
            loop.create_task(self.show_cover(artist.cover))

    async def show_album(self, album):
        loop = asyncio.get_event_loop()
        songs = await loop.run_in_executor(None, lambda: album.songs)
        self.table_overview.set_name(album.name)
        self.table_overview.set_desc(album.desc or '')
        self._show_songs(songs)
        if album.cover:
            loop.create_task(self.show_cover(album.cover))

    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_ctl.get(cover, cover_uid)
        img = QImage()
        img.loadFromData(content)
        pixmap = QPixmap(img)
        if not pixmap.isNull():
            self.table_overview.set_cover(pixmap)

    def _show_songs(self, songs):
        try:
            self.songs_table.song_deleted.disconnect()
        except TypeError:  # no connections at all
            pass
        self.show()
        self.songs_table.setModel(SongsTableModel(songs or []))
        self.songs_table.scrollToTop()

    def show_songs(self, songs):
        self._show_songs(songs)
        self.table_overview.hide()

    def search(self, text):
        if self.isVisible() and self.songs_table is not None:
            self.songs_table.filter_row(text)
Esempio n. 2
0
class SongsTableContainer(QFrame):
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.songs_table = SongsTableView(self)
        #: collections items view
        self.coll_items_table = CollectionItemsTable(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.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.songs_table.show_artist_needed.connect(
            lambda artist: asyncio.ensure_future(self.show_model(artist)))
        self.songs_table.show_album_needed.connect(
            lambda album: asyncio.ensure_future(self.show_model(album)))

        self.coll_items_table.show_url.connect(
            lambda url: asyncio.ensure_future(self.show_url(url)))

        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._top_layout = QHBoxLayout(self._top_container)
        self._layout = QVBoxLayout(self)
        self._cover_label.setMinimumWidth(200)
        self._left_sub_layout = QVBoxLayout()
        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)

        self._top_layout.addLayout(self._left_sub_layout)
        self._top_layout.addSpacing(20)
        self._top_layout.addLayout(self._right_sub_layout)
        self._top_layout.setStretch(1, 1)

        self.setAutoFillBackground(False)
        if use_mac_theme():
            self._layout.setContentsMargins(0, 0, 0, 0)
            self._layout.setSpacing(0)
        self._layout.addWidget(self._top_container)
        self._layout.addWidget(self.songs_table)
        self._layout.addWidget(self.coll_items_table)

        # 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):
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(None, 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
        self._app.histories.append(model)
        with self._app.create_action('show {}'.format(str(model))):
            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()
        songs = await loop.run_in_executor(None, lambda: playlist.songs)
        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()
        future_songs = loop.run_in_executor(None, lambda: artist.songs)
        future_desc = loop.run_in_executor(None, lambda: artist.desc)
        await asyncio.wait([future_songs, future_desc])
        desc = future_desc.result()
        self.set_desc(desc or '<h2>{}</h2>'.format(artist.name))
        self._show_songs(future_songs.result())
        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()
        future_songs = loop.run_in_executor(None, lambda: album.songs)
        future_desc = loop.run_in_executor(None, lambda: album.desc)
        await asyncio.wait([future_songs, future_desc])
        self.set_desc(future_desc.result() or
                      '<h2>{}</h2>'.format(album.name))
        songs = future_songs.result()
        self._show_songs(songs)
        if album.cover:
            loop.create_task(self.show_cover(album.cover))

    def show_collection(self, collection):
        self._top_container.hide()
        self.songs_table.hide()
        self.coll_items_table.show()
        self.coll_items_table.show_items(collection.items)

    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_ctl.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):
        try:
            self.songs_table.song_deleted.disconnect()
        except TypeError:  # no connections at all
            pass
        self.show()
        self.coll_items_table.hide()
        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()}
        self.songs_table.setModel(SongsTableModel(songs, source_name_map))
        self.songs_table.scrollToTop()

    def show_songs(self, songs):
        self._show_songs(songs)
        self._top_container.hide()

    def set_cover(self, pixmap):
        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)
Esempio n. 3
0
class SongsTableContainer(QFrame):
    def __init__(self, app, parent=None):
        super().__init__(parent)
        self._app = app

        self.songs_table = SongsTableView(self)
        self.table_overview = TableOverview(self)

        self._layout = QVBoxLayout(self)

        self.setAutoFillBackground(False)
        if use_mac_theme():
            self._layout.setContentsMargins(0, 0, 0, 0)
            self._layout.setSpacing(0)
        self._layout.addWidget(self.table_overview)
        self._layout.addWidget(self.songs_table)

        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: asyncio.ensure_future(self.show_model(artist)))
        self.songs_table.show_album_needed.connect(
            lambda album: asyncio.ensure_future(self.show_model(album)))
        self._cover_pixmap = None
        self.hide()

    async def play_song(self, song):
        loop = asyncio.get_event_loop()
        with self._app.create_action('play {}'.format(song)):
            await loop.run_in_executor(None, 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
        self._app.histories.append(model)
        with self._app.create_action('show {}'.format(str(model))):
            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):
        self.table_overview.show()
        loop = asyncio.get_event_loop()
        songs = await loop.run_in_executor(None, lambda: playlist.songs)
        self._show_songs(songs)
        desc = '<h2>{}</h2>\n{}'.format(playlist.name, playlist.desc or '')
        self.table_overview.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)
            # 如果有 f-string 该有多好!
            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.table_overview.show()
        loop = asyncio.get_event_loop()
        future_songs = loop.run_in_executor(None, lambda: artist.songs)
        future_desc = loop.run_in_executor(None, lambda: artist.desc)
        await asyncio.wait([future_songs, future_desc])
        desc = future_desc.result()
        self.table_overview.set_desc(desc or '<h2>{}</h2>'.format(artist.name))
        self._show_songs(future_songs.result())
        if artist.cover:
            loop.create_task(self.show_cover(artist.cover))

    async def show_album(self, album):
        loop = asyncio.get_event_loop()
        future_songs = loop.run_in_executor(None, lambda: album.songs)
        future_desc = loop.run_in_executor(None, lambda: album.desc)
        await asyncio.wait([future_songs, future_desc])
        self.table_overview.set_desc(future_desc.result() or
                                     '<h2>{}</h2>'.format(album.name))
        songs = future_songs.result()
        self._show_songs(songs)
        if album.cover:
            loop.create_task(self.show_cover(album.cover))

    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_ctl.get(cover, cover_uid)
        img = QImage()
        img.loadFromData(content)
        pixmap = QPixmap(img)
        if not pixmap.isNull():
            self.table_overview.set_cover(pixmap)
            self.update()

    def _show_songs(self, songs):
        try:
            self.songs_table.song_deleted.disconnect()
        except TypeError:  # no connections at all
            pass
        self.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()}
        self.songs_table.setModel(SongsTableModel(songs, source_name_map))
        self.songs_table.scrollToTop()

    def show_songs(self, songs):
        self._show_songs(songs)
        self.table_overview.hide()

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