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)
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, [])