Merge branch '27-playlist-artist-collapsed-view'

This commit is contained in:
Sumner Evans
2020-05-20 07:47:20 -06:00
9 changed files with 264 additions and 141 deletions

View File

@@ -164,6 +164,11 @@ class ConfigParamDescriptor:
UI.
* The literal string ``"option"``: corresponds to dropdown in the UI.
The :class:`hidden_behind` is an optional string representing the name of the
expander that the component should be displayed underneath. For example, one common
value for this is "Advanced" which will make the component only visible when the
user expands the "Advanced" settings.
The :class:`numeric_bounds` parameter only has an effect if the :class:`type` is
`int`. It specifies the min and max values that the UI control can have.
@@ -179,6 +184,7 @@ class ConfigParamDescriptor:
type: Union[Type, str]
description: str
required: bool = True
hidden_behind: Optional[str] = None
default: Any = None
numeric_bounds: Optional[Tuple[int, int]] = None
numeric_step: Optional[int] = None

View File

@@ -139,11 +139,15 @@ class FilesystemAdapter(CachingAdapter):
cache_key: CachingAdapter.CachedDataKey,
ignore_cache_miss: bool = False,
where_clauses: Tuple[Any, ...] = None,
order_by: Any = None,
) -> Sequence:
result = model.select()
if where_clauses is not None:
result = result.where(*where_clauses)
if order_by:
result = result.order_by(order_by)
if self.is_cache and not ignore_cache_miss:
# Determine if the adapter has ingested data for this key before, and if
# not, cache miss.
@@ -231,6 +235,7 @@ class FilesystemAdapter(CachingAdapter):
models.Playlist,
CachingAdapter.CachedDataKey.PLAYLISTS,
ignore_cache_miss=ignore_cache_miss,
order_by=fn.LOWER(models.Playlist.name),
)
return self._playlists
@@ -291,7 +296,10 @@ class FilesystemAdapter(CachingAdapter):
)
def get_albums(
self, query: AlbumSearchQuery, sort_direction: str = "ascending"
self,
query: AlbumSearchQuery,
sort_direction: str = "ascending"
# TODO deal with sort dir here?
) -> Sequence[API.Album]:
strhash = query.strhash()
query_result = models.AlbumQueryResult.get_or_none(
@@ -507,7 +515,7 @@ class FilesystemAdapter(CachingAdapter):
"year": getattr(api_album, "year", None),
"genre": ingest_genre_data(g) if (g := api_album.genre) else None,
"artist": ingest_artist_data(ar) if (ar := api_album.artist) else None,
"songs": [
"_songs": [
ingest_song_data(s, fill_album=False) for s in api_album.songs or []
],
"_cover_art": self._do_ingest_new_data(

View File

@@ -108,6 +108,15 @@ class Album(BaseModel):
except Exception:
return None
@property
def songs(self) -> List["Song"]:
albums = Album.select()
artists = Album.select()
return sorted(
prefetch(self._songs, albums, artists),
key=lambda s: (s.disc_number or 1, s.track),
)
class AlbumQueryResult(BaseModel):
query_hash = TextField(primary_key=True)
@@ -144,7 +153,7 @@ class Song(BaseModel):
duration = DurationField(null=True)
parent_id = TextField(null=True)
album = ForeignKeyField(Album, null=True, backref="songs")
album = ForeignKeyField(Album, null=True, backref="_songs")
artist = ForeignKeyField(Artist, null=True)
genre = ForeignKeyField(Genre, null=True, backref="songs")

View File

@@ -125,7 +125,7 @@ class SubsonicAdapter(Adapter):
self.ping_process = multiprocessing.Process(target=self._check_ping_thread)
self.ping_process.start()
# TODO (#191): support XML?
# TODO (#112): support XML?
def initial_sync(self):
# Wait for the ping to happen.

View File

@@ -61,14 +61,11 @@
}
#playlist-album-artwork {
min-height: 200px;
min-width: 200px;
margin: 10px;
margin: 10px 15px 0 10px;
}
#playlist-album-artwork.collapsed {
min-height: 40px;
min-width: 40px;
#artist-info-panel {
margin-bottom: 10px;
}
#playlist-name, #artist-detail-panel #artist-name {
@@ -76,6 +73,11 @@
margin-bottom: 10px;
}
#playlist-name.collapsed,
#artist-detail-panel #artist-name.collapsed {
font-size: 30px;
}
#playlist-comment, #playlist-stats, #artist-bio, #artist-stats, #similar-artists {
margin-bottom: 10px;
}
@@ -85,10 +87,6 @@
margin: -10px 0 0 10px;
}
#playlist-play-shuffle-buttons {
margin-bottom: 10px;
}
/* ********** Playback Controls ********** */
#player-controls-album-artwork {
min-height: 70px;
@@ -211,9 +209,7 @@
}
#artist-album-artwork {
margin: 10px;
min-width: 300px;
min-height: 300px;
margin: 10px 15px 0 10px;
}
#artist-album-list-artwork {

View File

@@ -1,6 +1,6 @@
from datetime import timedelta
from random import randint
from typing import Any, List, Sequence
from typing import List, Sequence
from gi.repository import Gio, GLib, GObject, Gtk, Pango
@@ -36,6 +36,9 @@ class ArtistsPanel(Gtk.Paned):
self.artist_detail_panel.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
)
self.artist_detail_panel.connect(
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
)
self.pack2(self.artist_detail_panel, True, False)
def update(self, app_config: AppConfiguration, force: bool = False):
@@ -145,7 +148,7 @@ class ArtistList(Gtk.Box):
self.loading_indicator.hide()
class ArtistDetailPanel(Gtk.ScrolledWindow):
class ArtistDetailPanel(Gtk.Box):
"""Defines the artists list."""
__gsignals__ = {
@@ -154,19 +157,30 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
GObject.TYPE_NONE,
(int, object, object),
),
"refresh-window": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
(object, bool),
),
}
update_order_token = 0
artist_details_expanded = False
def __init__(self, *args, **kwargs):
super().__init__(*args, name="artist-detail-panel", **kwargs)
super().__init__(
*args,
name="artist-detail-panel",
orientation=Gtk.Orientation.VERTICAL,
**kwargs,
)
self.albums: Sequence[API.Album] = []
self.artist_id = None
artist_info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Artist info panel
self.big_info_panel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.big_info_panel = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, name="artist-info-panel"
)
self.artist_artwork = SpinnerImage(
loading=False,
@@ -179,22 +193,6 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
# Action buttons, name, comment, number of songs, etc.
artist_details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Action buttons (note we are packing end here, so we have to put them
# in right-to-left).
self.artist_action_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
view_refresh_button = IconButton("view-refresh-symbolic", "Refresh artist info")
view_refresh_button.connect("clicked", self.on_view_refresh_click)
self.artist_action_buttons.pack_end(view_refresh_button, False, False, 5)
download_all_btn = IconButton(
"folder-download-symbolic", "Download all songs by this artist"
)
download_all_btn.connect("clicked", self.on_download_all_click)
self.artist_action_buttons.pack_end(download_all_btn, False, False, 5)
artist_details_box.pack_start(self.artist_action_buttons, False, False, 5)
artist_details_box.pack_start(Gtk.Box(), True, False, 0)
self.artist_indicator = self.make_label(name="artist-indicator")
@@ -246,35 +244,55 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
self.big_info_panel.pack_start(artist_details_box, True, True, 10)
artist_info_box.pack_start(self.big_info_panel, False, True, 0)
# Action buttons
action_buttons_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.artist_action_buttons = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=10
)
download_all_btn = IconButton(
"folder-download-symbolic", "Download all songs by this artist"
)
download_all_btn.connect("clicked", self.on_download_all_click)
self.artist_action_buttons.add(download_all_btn)
view_refresh_button = IconButton("view-refresh-symbolic", "Refresh artist info")
view_refresh_button.connect("clicked", self.on_view_refresh_click)
self.artist_action_buttons.add(view_refresh_button)
action_buttons_container.pack_start(
self.artist_action_buttons, False, False, 10
)
action_buttons_container.pack_start(Gtk.Box(), True, True, 0)
expand_button_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.expand_collapse_button = IconButton(
"pan-up-symbolic", "Expand playlist details"
)
self.expand_collapse_button.connect("clicked", self.on_expand_collapse_click)
expand_button_container.pack_end(self.expand_collapse_button, False, False, 0)
action_buttons_container.add(expand_button_container)
self.big_info_panel.pack_start(action_buttons_container, False, False, 5)
self.pack_start(self.big_info_panel, False, True, 0)
self.album_list_scrolledwindow = Gtk.ScrolledWindow()
self.albums_list = AlbumsListWithSongs()
self.albums_list.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
)
artist_info_box.pack_start(self.albums_list, True, True, 0)
self.add(artist_info_box)
self.album_list_scrolledwindow.add(self.albums_list)
self.pack_start(self.album_list_scrolledwindow, True, True, 0)
def update(self, app_config: AppConfiguration):
self.artist_id = app_config.state.selected_artist_id
if app_config.state.selected_artist_id is None:
self.artist_action_buttons.hide()
self.artist_indicator.set_text("")
self.artist_name.set_markup("")
self.artist_stats.set_markup("")
self.artist_bio.set_markup("")
self.similar_artists_scrolledwindow.hide()
self.play_shuffle_buttons.hide()
self.artist_artwork.set_from_file(None)
self.albums = []
self.albums_list.update(None)
self.hide()
else:
self.update_order_token += 1
self.artist_action_buttons.show()
self.show()
self.update_artist_view(
app_config.state.selected_artist_id,
app_config=app_config,
@@ -296,28 +314,50 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
if order_token != self.update_order_token:
return
self.artist_indicator.set_text("ARTIST")
if app_config:
self.artist_details_expanded = app_config.state.artist_details_expanded
up_down = "up" if self.artist_details_expanded else "down"
self.expand_collapse_button.set_icon(f"pan-{up_down}-symbolic")
self.expand_collapse_button.set_tooltip_text(
"Collapse" if self.artist_details_expanded else "Expand"
)
self.artist_name.set_markup(util.esc(f"<b>{artist.name}</b>"))
self.artist_stats.set_markup(self.format_stats(artist))
self.artist_name.set_tooltip_text(artist.name)
self.artist_bio.set_markup(util.esc(artist.biography))
if self.artist_details_expanded:
self.show_all()
self.artist_artwork.set_image_size(300)
self.artist_indicator.set_text("ARTIST")
self.artist_stats.set_markup(self.format_stats(artist))
if len(artist.similar_artists or []) > 0:
self.similar_artists_label.set_markup("<b>Similar Artists:</b> ")
for c in self.similar_artists_button_box.get_children():
self.similar_artists_button_box.remove(c)
self.artist_bio.set_markup(util.esc(artist.biography))
for artist in (artist.similar_artists or [])[:5]:
self.similar_artists_button_box.add(
Gtk.LinkButton(
label=artist.name,
name="similar-artist-button",
action_name="app.go-to-artist",
action_target=GLib.Variant("s", artist.id),
if len(artist.similar_artists or []) > 0:
self.similar_artists_label.set_markup("<b>Similar Artists:</b> ")
for c in self.similar_artists_button_box.get_children():
self.similar_artists_button_box.remove(c)
for artist in (artist.similar_artists or [])[:5]:
self.similar_artists_button_box.add(
Gtk.LinkButton(
label=artist.name,
name="similar-artist-button",
action_name="app.go-to-artist",
action_target=GLib.Variant("s", artist.id),
)
)
)
self.similar_artists_scrolledwindow.show_all()
self.similar_artists_scrolledwindow.show_all()
else:
self.similar_artists_scrolledwindow.hide()
else:
self.artist_name.get_style_context().add_class("collapsed")
self.show_all()
self.artist_artwork.set_image_size(70)
self.artist_indicator.hide()
self.artist_stats.hide()
self.artist_bio.hide()
self.similar_artists_scrolledwindow.hide()
self.play_shuffle_buttons.show_all()
@@ -353,7 +393,7 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
self.artist_id, force=True, order_token=self.update_order_token,
)
def on_download_all_click(self, btn: Any):
def on_download_all_click(self, _):
AdapterManager.batch_download_songs(
self.get_artist_song_ids(),
before_download=lambda _: self.update_artist_view(
@@ -364,13 +404,13 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
),
)
def on_play_all_clicked(self, btn: Any):
def on_play_all_clicked(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked", 0, songs, {"force_shuffle_state": False},
)
def on_shuffle_all_button(self, btn: Any):
def on_shuffle_all_button(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked",
@@ -379,6 +419,13 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
{"force_shuffle_state": True},
)
def on_expand_collapse_click(self, _):
self.emit(
"refresh-window",
{"artist_details_expanded": not self.artist_details_expanded},
False,
)
# Helper Methods
# =========================================================================
def set_all_loading(self, loading_state: bool):

View File

@@ -14,6 +14,7 @@ class SpinnerImage(Gtk.Overlay):
):
Gtk.Overlay.__init__(self)
self.image_size = image_size
self.filename: Optional[str] = None
self.image = Gtk.Image(name=image_name, **kwargs)
self.add(self.image)
@@ -29,6 +30,7 @@ class SpinnerImage(Gtk.Overlay):
def set_from_file(self, filename: Optional[str]):
if filename == "":
filename = None
self.filename = filename
if self.image_size is not None and filename:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
filename, self.image_size, self.image_size, True,
@@ -44,3 +46,7 @@ class SpinnerImage(Gtk.Overlay):
else:
self.spinner.stop()
self.spinner.hide()
def set_image_size(self, size: int):
self.image_size = size
self.set_from_file(self.filename)

View File

@@ -153,7 +153,6 @@ class PlaylistList(Gtk.Box):
margin=10,
halign=Gtk.Align.START,
ellipsize=Pango.EllipsizeMode.END,
max_width_chars=30,
)
)
row.show_all()
@@ -204,25 +203,25 @@ class PlaylistList(Gtk.Box):
# Event Handlers
# =========================================================================
def on_new_playlist_clicked(self, _: Any):
def on_new_playlist_clicked(self, _):
self.new_playlist_entry.set_text("Untitled Playlist")
self.new_playlist_entry.grab_focus()
self.new_playlist_row.show()
def on_list_refresh_click(self, _: Any):
def on_list_refresh_click(self, _):
self.update(force=True)
def new_entry_activate(self, entry: Gtk.Entry):
self.create_playlist(entry.get_text())
def cancel_button_clicked(self, _: Any):
def cancel_button_clicked(self, _):
self.new_playlist_row.hide()
def confirm_button_clicked(self, _: Any):
def confirm_button_clicked(self, _):
self.create_playlist(self.new_playlist_entry.get_text())
def create_playlist(self, playlist_name: str):
def on_playlist_created(_: Any):
def on_playlist_created(_):
self.update(force=True)
self.loading_indicator.show()
@@ -247,54 +246,27 @@ class PlaylistDetailPanel(Gtk.Overlay):
}
playlist_id = None
playlist_details_expanded = False
editing_playlist_song_list: bool = False
reordering_playlist_song_list: bool = False
def __init__(self):
Gtk.Overlay.__init__(self, name="playlist-view-overlay")
playlist_view_scroll_window = Gtk.ScrolledWindow()
playlist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.playlist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Playlist info panel
self.big_info_panel = Gtk.Box(
name="playlist-info-panel", orientation=Gtk.Orientation.HORIZONTAL,
)
playlist_info_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.playlist_artwork = SpinnerImage(
image_name="playlist-album-artwork",
spinner_name="playlist-artwork-spinner",
image_size=200,
)
self.big_info_panel.pack_start(self.playlist_artwork, False, False, 0)
playlist_info_box.add(self.playlist_artwork)
# Action buttons, name, comment, number of songs, etc.
# Name, comment, number of songs, etc.
playlist_details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Action buttons (note we are packing end here, so we have to put them
# in right-to-left).
self.playlist_action_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
view_refresh_button = IconButton(
"view-refresh-symbolic", "Refresh playlist info"
)
view_refresh_button.connect("clicked", self.on_view_refresh_click)
self.playlist_action_buttons.pack_end(view_refresh_button, False, False, 5)
playlist_edit_button = IconButton("document-edit-symbolic", "Edit paylist")
playlist_edit_button.connect("clicked", self.on_playlist_edit_button_click)
self.playlist_action_buttons.pack_end(playlist_edit_button, False, False, 5)
download_all_button = IconButton(
"folder-download-symbolic", "Download all songs in the playlist"
)
download_all_button.connect(
"clicked", self.on_playlist_list_download_all_button_click
)
self.playlist_action_buttons.pack_end(download_all_button, False, False, 5)
playlist_details_box.pack_start(self.playlist_action_buttons, False, False, 5)
playlist_details_box.pack_start(Gtk.Box(), True, False, 0)
self.playlist_indicator = self.make_label(name="playlist-indicator")
@@ -328,11 +300,53 @@ class PlaylistDetailPanel(Gtk.Overlay):
playlist_details_box.add(self.play_shuffle_buttons)
self.big_info_panel.pack_start(playlist_details_box, True, True, 10)
playlist_info_box.pack_start(playlist_details_box, True, True, 0)
playlist_box.pack_start(self.big_info_panel, False, True, 0)
# Action buttons & expand/collapse button
action_buttons_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.playlist_action_buttons = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=10
)
download_all_button = IconButton(
"folder-download-symbolic", "Download all songs in the playlist"
)
download_all_button.connect(
"clicked", self.on_playlist_list_download_all_button_click
)
self.playlist_action_buttons.add(download_all_button)
playlist_edit_button = IconButton("document-edit-symbolic", "Edit paylist")
playlist_edit_button.connect("clicked", self.on_playlist_edit_button_click)
self.playlist_action_buttons.add(playlist_edit_button)
view_refresh_button = IconButton(
"view-refresh-symbolic", "Refresh playlist info"
)
view_refresh_button.connect("clicked", self.on_view_refresh_click)
self.playlist_action_buttons.add(view_refresh_button)
action_buttons_container.pack_start(
self.playlist_action_buttons, False, False, 10
)
action_buttons_container.pack_start(Gtk.Box(), True, True, 0)
expand_button_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.expand_collapse_button = IconButton(
"pan-up-symbolic", "Expand playlist details"
)
self.expand_collapse_button.connect("clicked", self.on_expand_collapse_click)
expand_button_container.pack_end(self.expand_collapse_button, False, False, 0)
action_buttons_container.add(expand_button_container)
playlist_info_box.pack_end(action_buttons_container, False, False, 5)
self.playlist_box.add(playlist_info_box)
# Playlist songs list
playlist_view_scroll_window = Gtk.ScrolledWindow()
self.playlist_song_store = Gtk.ListStore(
str, # cache status
str, # title
@@ -399,10 +413,10 @@ class PlaylistDetailPanel(Gtk.Overlay):
)
self.playlist_song_store.connect("row-deleted", self.on_playlist_model_row_move)
playlist_box.add(self.playlist_songs)
playlist_view_scroll_window.add(self.playlist_songs)
playlist_view_scroll_window.add(playlist_box)
self.add(playlist_view_scroll_window)
self.playlist_box.pack_start(playlist_view_scroll_window, True, True, 0)
self.add(self.playlist_box)
playlist_view_spinner = Gtk.Spinner(active=True)
playlist_view_spinner.start()
@@ -417,17 +431,11 @@ class PlaylistDetailPanel(Gtk.Overlay):
def update(self, app_config: AppConfiguration, force: bool = False):
if app_config.state.selected_playlist_id is None:
self.playlist_artwork.set_from_file(None)
self.playlist_indicator.set_markup("")
self.playlist_name.set_markup("")
self.playlist_comment.hide()
self.playlist_stats.set_markup("")
self.playlist_action_buttons.hide()
self.play_shuffle_buttons.hide()
self.playlist_box.hide()
self.playlist_view_loading_box.hide()
self.playlist_artwork.set_loading(False)
else:
self.update_playlist_view_order_token += 1
self.playlist_box.show()
self.update_playlist_view(
app_config.state.selected_playlist_id,
app_config=app_config,
@@ -459,15 +467,40 @@ class PlaylistDetailPanel(Gtk.Overlay):
self.playlist_id = playlist.id
if app_config:
self.playlist_details_expanded = app_config.state.playlist_details_expanded
up_down = "up" if self.playlist_details_expanded else "down"
self.expand_collapse_button.set_icon(f"pan-{up_down}-symbolic")
self.expand_collapse_button.set_tooltip_text(
"Collapse" if self.playlist_details_expanded else "Expand"
)
# Update the info display.
self.playlist_indicator.set_markup("PLAYLIST")
self.playlist_name.set_markup(f"<b>{playlist.name}</b>")
if playlist.comment:
self.playlist_comment.set_text(playlist.comment)
self.playlist_comment.show()
self.playlist_name.set_tooltip_text(playlist.name)
if self.playlist_details_expanded:
self.playlist_name.get_style_context().remove_class("collapsed")
self.playlist_box.show_all()
self.playlist_artwork.set_image_size(200)
self.playlist_indicator.set_markup("PLAYLIST")
if playlist.comment:
self.playlist_comment.set_text(playlist.comment)
self.playlist_comment.set_tooltip_text(playlist.comment)
self.playlist_comment.show()
else:
self.playlist_comment.hide()
self.playlist_stats.set_markup(self._format_stats(playlist))
else:
self.playlist_name.get_style_context().add_class("collapsed")
self.playlist_box.show_all()
self.playlist_artwork.set_image_size(70)
self.playlist_indicator.hide()
self.playlist_comment.hide()
self.playlist_stats.set_markup(self._format_stats(playlist))
self.playlist_stats.hide()
# Update the artwork.
self.update_playlist_artwork(playlist.cover_art, order_token=order_token)
@@ -522,7 +555,6 @@ class PlaylistDetailPanel(Gtk.Overlay):
self.playlist_view_loading_box.hide()
self.playlist_action_buttons.show_all()
self.play_shuffle_buttons.show_all()
@util.async_callback(
AdapterManager.get_cover_art_filename,
@@ -544,14 +576,14 @@ class PlaylistDetailPanel(Gtk.Overlay):
# Event Handlers
# =========================================================================
def on_view_refresh_click(self, _: Any):
def on_view_refresh_click(self, _):
self.update_playlist_view(
self.playlist_id,
force=True,
order_token=self.update_playlist_view_order_token,
)
def on_playlist_edit_button_click(self, _: Any):
def on_playlist_edit_button_click(self, _):
assert self.playlist_id
playlist = AdapterManager.get_playlist_details(self.playlist_id).result()
dialog = EditPlaylistDialog(self.get_toplevel(), playlist)
@@ -609,7 +641,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
dialog.destroy()
def on_playlist_list_download_all_button_click(self, _: Any):
def on_playlist_list_download_all_button_click(self, _):
def download_state_change(song_id: str):
GLib.idle_add(
lambda: self.update_playlist_view(
@@ -624,7 +656,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
on_song_download_complete=download_state_change,
)
def on_play_all_clicked(self, _: Any):
def on_play_all_clicked(self, _):
self.emit(
"song-clicked",
0,
@@ -632,7 +664,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
{"force_shuffle_state": False, "active_playlist_id": self.playlist_id},
)
def on_shuffle_all_button(self, _: Any):
def on_shuffle_all_button(self, _):
self.emit(
"song-clicked",
randint(0, len(self.playlist_song_store) - 1),
@@ -640,7 +672,14 @@ class PlaylistDetailPanel(Gtk.Overlay):
{"force_shuffle_state": True, "active_playlist_id": self.playlist_id},
)
def on_song_activated(self, _: Any, idx: Gtk.TreePath, col: Any):
def on_expand_collapse_click(self, _):
self.emit(
"refresh-window",
{"playlist_details_expanded": not self.playlist_details_expanded},
False,
)
def on_song_activated(self, _, idx: Gtk.TreePath, col: Any):
# The song ID is in the last column of the model.
self.emit(
"song-clicked",
@@ -678,7 +717,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
bin_coords = tree.convert_tree_to_bin_window_coords(event.x, event.y)
widget_coords = tree.convert_tree_to_widget_coords(event.x, event.y)
def on_remove_songs_click(_: Any):
def on_remove_songs_click(_):
assert self.playlist_id
delete_idxs = {p.get_indices()[0] for p in paths}
new_song_ids = [
@@ -737,7 +776,13 @@ class PlaylistDetailPanel(Gtk.Overlay):
self.playlist_view_loading_box.show_all()
def make_label(self, text: str = None, name: str = None, **params,) -> Gtk.Label:
return Gtk.Label(label=text, name=name, halign=Gtk.Align.START, **params,)
return Gtk.Label(
label=text,
name=name,
halign=Gtk.Align.START,
ellipsize=Pango.EllipsizeMode.END,
**params,
)
@util.async_callback(AdapterManager.get_playlist_details)
def _update_playlist_order(

View File

@@ -43,6 +43,8 @@ class UIState:
)
version: int = 1
# Play state
playing: bool = False
current_song_index: int = -1
play_queue: Tuple[str, ...] = field(default_factory=tuple)
@@ -54,6 +56,8 @@ class UIState:
song_progress: timedelta = timedelta()
song_stream_cache_progress: Optional[timedelta] = timedelta()
current_device: str = "this device"
# UI state
current_tab: str = "albums"
selected_album_id: Optional[str] = None
selected_artist_id: Optional[str] = None
@@ -63,6 +67,8 @@ class UIState:
album_page_size: int = 30
album_page: int = 0
current_notification: Optional[UINotification] = None
playlist_details_expanded: bool = True
artist_details_expanded: bool = True
def __getstate__(self):
state = self.__dict__.copy()