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 __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()
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)
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)
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)