This commit is contained in:
Benjamin Schaaf
2022-01-03 02:34:46 +11:00
parent 5c4a29e6ae
commit 0f3e04007f
7 changed files with 165 additions and 225 deletions

View File

@@ -10,6 +10,8 @@ NoneType = type(None)
def run_action(widget, name, *args):
# print('run action', name, args)
group, action = name.split('.')
action_group = widget.get_action_group(group)

View File

@@ -167,6 +167,7 @@ class AlbumsPanel(Handy.Leaflet):
self.album_with_songs.get_style_context().add_class("details-panel")
def back_clicked(_):
self.grid.unselect_all()
self.set_visible_child(self.grid_box)
self.album_with_songs.connect("back-clicked", back_clicked)

View File

@@ -16,24 +16,12 @@ from ..adapters import (
from ..config import AppConfiguration
from ..ui import util
from ..ui.common import AlbumWithSongs, IconButton, IconToggleButton, LoadError, SpinnerImage, Sizer
from .actions import run_action
class ArtistsPanel(Handy.Leaflet):
"""Defines the arist panel."""
__gsignals__ = {
"song-clicked": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
(int, object, object),
),
"refresh-window": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
(object, bool),
),
}
def __init__(self, *args, **kwargs):
super().__init__(transition_type=Handy.LeafletTransitionType.SLIDE, can_swipe_forward=False, interpolate_size=False)
@@ -44,14 +32,6 @@ class ArtistsPanel(Handy.Leaflet):
details_sizer = Sizer(hexpand=True, natural_width=800)
self.artist_detail_panel = ArtistDetailPanel()
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),
)
details_sizer.add(self.artist_detail_panel)
self.add(details_sizer)
@@ -210,16 +190,6 @@ class ArtistDetailPanel(Gtk.Box):
__gsignals__ = {
"back-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, ()),
"song-clicked": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
(int, object, object),
),
"refresh-window": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
(object, bool),
),
}
show_mobile = GObject.Property(type=bool, default=False)
@@ -333,10 +303,6 @@ class ArtistDetailPanel(Gtk.Box):
# self.add(self.error_container)
self.albums_list = AlbumsListWithSongs()
self.albums_list.connect(
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
box.pack_start(self.albums_list, True, True, 0)
self.scrolled_window.add(box)
@@ -511,21 +477,12 @@ class ArtistDetailPanel(Gtk.Box):
def on_play_all_clicked(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked",
0,
songs,
{"force_shuffle_state": False},
)
run_action(self, 'app.play-song', 0, songs, {"force_shuffle_state": False})
def on_shuffle_all_button(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked",
randint(0, len(songs) - 1),
songs,
{"force_shuffle_state": True},
)
song_idx = randint(0, len(songs) - 1)
run_action(self, 'app.play-song', song_idx, songs, {"force_shuffle_state": True})
# Helper Methods
# =========================================================================
@@ -582,14 +539,6 @@ class ArtistDetailPanel(Gtk.Box):
class AlbumsListWithSongs(Gtk.Overlay):
__gsignals__ = {
"song-clicked": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
(int, object, object),
),
}
def __init__(self):
Gtk.Overlay.__init__(self)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

View File

@@ -239,9 +239,7 @@ class MainWindow(Handy.ApplicationWindow):
icon_status = status_label.split()[0].lower()
self.server_connection_menu_button.set_icon(
f"{icon_basename}-{icon_status}-symbolic"
)
server_menu_button_icon = f"{icon_basename}-{icon_status}-symbolic"
self.connection_status_icon.set_from_icon_name(
f"server-{icon_status}-symbolic",
Gtk.IconSize.BUTTON,
@@ -249,9 +247,12 @@ class MainWindow(Handy.ApplicationWindow):
self.connection_status_label.set_text(status_label)
self.connected_status_box.show_all()
else:
self.server_connection_menu_button.set_icon(f"{icon_basename}-symbolic")
server_menu_button_icon = f"{icon_basename}-symbolic"
self.connected_status_box.hide()
self.sidebar_server_menu_button.set_icon(server_menu_button_icon)
self.headerbar_server_menu_button.set_icon(server_menu_button_icon)
self._updating_settings = True
# Offline Mode Settings
@@ -435,6 +436,7 @@ class MainWindow(Handy.ApplicationWindow):
self.player_manager.update_song_progress(progress, duration, cache_progess)
def update_song_download_progress(self, song_id: str, progress: DownloadProgress):
return
if progress.type == DownloadProgress.Type.QUEUED:
if (
song_id not in self._failed_downloads
@@ -609,14 +611,12 @@ class MainWindow(Handy.ApplicationWindow):
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
# Downloads
self.downloads_popover = self._create_downloads_popover()
self.downloads_menu_button = IconMenuButton(
self.downloads_menu_button = IconButton(
"folder-download-symbolic",
tooltip_text="Show download status",
popover=self.downloads_popover,
relief=True,
)
self.downloads_menu_button.connect("clicked", self._on_downloads_menu_clicked)
self.downloads_popover.set_relative_to(self.downloads_menu_button)
self.downloads_menu_button.connect("clicked", self._show_downloads)
button_box.add(self.downloads_menu_button)
# Preferences
@@ -630,18 +630,18 @@ class MainWindow(Handy.ApplicationWindow):
# Server icon and change server dropdown
self.server_connection_popover = self._create_server_connection_popover()
self.server_connection_menu_button = IconMenuButton(
self.headerbar_server_menu_button = IconMenuButton(
"list-add-symbolic",
tooltip_text="Server connection settings",
popover=self.server_connection_popover,
)
self.server_connection_menu_button.connect(
self.headerbar_server_menu_button.connect(
"clicked", self._on_server_connection_menu_clicked
)
self.server_connection_popover.set_relative_to(
self.server_connection_menu_button
self.headerbar_server_menu_button
)
button_box.add(self.server_connection_menu_button)
button_box.add(self.headerbar_server_menu_button)
desktop_header.pack_end(button_box)
@@ -682,14 +682,15 @@ class MainWindow(Handy.ApplicationWindow):
box.pack_end(Gtk.Separator(), False, False, 0)
servers = IconButton("list-add-symbolic", label="Servers")
box.pack_end(servers, False, False, 0)
self.sidebar_server_menu_button = IconButton("list-add-symbolic", label="Servers")
box.pack_end(self.sidebar_server_menu_button, False, False, 0)
settings = IconButton("emblem-system-symbolic", label="Settings")
settings.connect("clicked", self._show_settings)
box.pack_end(settings, False, False, 0)
downloads = IconButton("folder-download-symbolic", label="Downloads")
downloads.connect('clicked', self._show_downloads)
box.pack_end(downloads, False, False, 0)
return box
@@ -768,64 +769,6 @@ class MainWindow(Handy.ApplicationWindow):
return box
def _create_downloads_popover(self) -> Gtk.PopoverMenu:
menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="downloads-menu")
current_downloads_header = Gtk.Box()
current_downloads_header.add(
current_downloads_label := Gtk.Label(
label="Current Downloads",
name="menu-header",
)
)
current_downloads_label.get_style_context().add_class("menu-label")
self.cancel_all_button = IconButton(
"process-stop-symbolic", "Cancel all downloads", sensitive=False
)
self.cancel_all_button.connect("clicked", self._on_cancel_all_clicked)
current_downloads_header.pack_end(self.cancel_all_button, False, False, 0)
vbox.add(current_downloads_header)
self.current_downloads_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, name="current-downloads-list"
)
self._current_downloads_placeholder = Gtk.Label(
label="<i>No current downloads</i>",
use_markup=True,
name="current-downloads-list-placeholder",
)
self.current_downloads_box.add(self._current_downloads_placeholder)
vbox.add(self.current_downloads_box)
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
clear_cache = self._create_model_button("Clear Cache", menu_name="clear-cache")
vbox.add(clear_cache)
menu.add(vbox)
# Create the "Add song(s) to playlist" sub-menu.
clear_cache_options = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Back button
clear_cache_options.add(
Gtk.ModelButton(inverted=True, centered=True, menu_name="main")
)
# Clear Song File Cache
menu_items = [
("Delete Cached Song Files", self._clear_song_file_cache),
("Delete Cached Song Files and Metadata", self._clear_entire_cache),
]
for text, clicked_fn in menu_items:
clear_song_cache = self._create_model_button(text, clicked_fn=clicked_fn)
clear_cache_options.pack_start(clear_song_cache, False, True, 0)
menu.add(clear_cache_options)
menu.child_set_property(clear_cache_options, "submenu", "clear-cache")
return menu
def _create_server_connection_popover(self) -> Gtk.PopoverMenu:
menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -899,69 +842,6 @@ class MainWindow(Handy.ApplicationWindow):
menu.child_set_property(switch_provider_options, "submenu", "switch-provider")
return menu
def _create_settings_window(self) -> Gtk.PopoverMenu:
window = Handy.PreferencesWindow(can_swipe_back=True)
general = Handy.PreferencesPage(icon_name="emblem-system-symbolic", title="General")
general_group = Handy.PreferencesGroup(title="General")
def create_switch(setting, **kwargs):
row = Handy.ActionRow(**kwargs)
switch = Gtk.Switch(valign=Gtk.Align.CENTER)
prop = setting.replace("_", "-")
self.bind_property(f"setting-{prop}", switch, "active", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
row.add(switch)
return row
def create_spin_button(low: int, high: int, step: int, setting: str, **kwargs):
row = Handy.ActionRow(**kwargs)
button = Gtk.SpinButton.new_with_range(low, high, step)
prop = setting.replace("_", "-")
self.bind_property(f"setting-{prop}", button, "value", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
row.add(button)
return row
# Notifications
row = create_switch("song_play_notification", title="Enable Song Notifications")
general_group.add(row)
# Player settings
self.player_settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# vbox.add(self.player_settings_box)
general.add(general_group)
window.add(general)
# DOWNLOAD SETTINGS
# ==============================================================================
download = Handy.PreferencesPage(icon_name="folder-download-symbolic", title="Downloads")
download_group = Handy.PreferencesGroup(title="Downloads", description="Settings for downloads")
# Allow Song Downloads
download_row = Handy.ExpanderRow(show_enable_switch=True, title="Allow Song Downloads", expanded=True)
self.bind_property("setting-allow-song-downloads", download_row, "enable-expansion", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
# Download on Stream
row = create_switch("download_on_stream", title="When Streaming, Also Download Song")
download_row.add(row)
# Prefetch Songs
row = create_spin_button(0, 10, 1, "prefetch_amount", title="Number of Songs to Prefetch")
download_row.add(row)
# Max Concurrent Downloads
row = create_spin_button(0, 10, 1, "concurrent_download_limit", title="Maximum Concurrent Downloads")
download_row.add(row)
download_group.add(download_row)
download.add(download_group)
window.add(download)
return window
def _on_settings_change(self, setting, _, prop):
if self._updating_settings:
return
@@ -1044,14 +924,10 @@ class MainWindow(Handy.ApplicationWindow):
confirm_dialog = Gtk.MessageDialog(
transient_for=self.get_toplevel(),
message_type=Gtk.MessageType.WARNING,
buttons=(
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_DELETE,
Gtk.ResponseType.YES,
),
text=title,
)
confirm_dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
confirm_dialog.add_button(Gtk.STOCK_DELETE, Gtk.ResponseType.YES)
confirm_dialog.format_secondary_markup(detail_text)
result = confirm_dialog.run()
confirm_dialog.destroy()
@@ -1071,16 +947,129 @@ class MainWindow(Handy.ApplicationWindow):
AdapterManager.clear_entire_cache()
self.emit("refresh-window", {}, True)
def _on_downloads_menu_clicked(self, *args):
self.downloads_popover.popup()
self.downloads_popover.show_all()
def _on_server_connection_menu_clicked(self, *args):
self.server_connection_popover.popup()
self.server_connection_popover.show_all()
def _show_downloads(self, *args):
window = Handy.Window(
modal=True,
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
destroy_with_parent=True,
type_hint=Gdk.WindowTypeHint.DIALOG)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
header_bar = Handy.HeaderBar(show_close_button=True, title="Downloads")
box.add(header_bar)
current_downloads_header = Gtk.Box()
current_downloads_header.add(
current_downloads_label := Gtk.Label(
label="Current Downloads",
name="menu-header",
)
)
current_downloads_label.get_style_context().add_class("menu-label")
self.cancel_all_button = IconButton(
"process-stop-symbolic", "Cancel all downloads", sensitive=False
)
self.cancel_all_button.connect("clicked", self._on_cancel_all_clicked)
current_downloads_header.pack_end(self.cancel_all_button, False, False, 0)
box.add(current_downloads_header)
self.current_downloads_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, name="current-downloads-list"
)
self._current_downloads_placeholder = Gtk.Label(
label="<i>No current downloads</i>",
use_markup=True,
name="current-downloads-list-placeholder",
)
self.current_downloads_box.add(self._current_downloads_placeholder)
box.pack_start(self.current_downloads_box, True, True, 5)
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
button = Gtk.Button(label="Clear Song Cache")
button.get_style_context().add_class('destructive-action')
button.connect("clicked", self._clear_song_file_cache)
button_box.pack_end(button, False, False, 5)
button = Gtk.Button(label="Clear Full Cache")
button.get_style_context().add_class('destructive-action')
button.connect("clicked", self._clear_entire_cache)
button_box.pack_end(button, False, False, 5)
box.add(button_box)
window.add(box)
self._show_transient_window(window)
def _show_settings(self, *args):
window = self._create_settings_window()
window = Handy.PreferencesWindow()
general = Handy.PreferencesPage(icon_name="emblem-system-symbolic", title="General")
general_group = Handy.PreferencesGroup(title="General")
def create_switch(setting, **kwargs):
row = Handy.ActionRow(**kwargs)
switch = Gtk.Switch(valign=Gtk.Align.CENTER)
prop = setting.replace("_", "-")
self.bind_property(f"setting-{prop}", switch, "active", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
row.add(switch)
return row
def create_spin_button(low: int, high: int, step: int, setting: str, **kwargs):
row = Handy.ActionRow(**kwargs)
button = Gtk.SpinButton.new_with_range(low, high, step)
prop = setting.replace("_", "-")
self.bind_property(f"setting-{prop}", button, "value", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
row.add(button)
return row
# Notifications
row = create_switch("song_play_notification", title="Enable Song Notifications")
general_group.add(row)
# Player settings
self.player_settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# vbox.add(self.player_settings_box)
general.add(general_group)
window.add(general)
# DOWNLOAD SETTINGS
# ==============================================================================
download = Handy.PreferencesPage(icon_name="folder-download-symbolic", title="Downloads")
download_group = Handy.PreferencesGroup(title="Downloads", description="Settings for downloads")
# Allow Song Downloads
download_row = Handy.ExpanderRow(show_enable_switch=True, title="Allow Song Downloads", expanded=True)
self.bind_property("setting-allow-song-downloads", download_row, "enable-expansion", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
# Download on Stream
row = create_switch("download_on_stream", title="When Streaming, Also Download Song")
download_row.add(row)
# Prefetch Songs
row = create_spin_button(0, 10, 1, "prefetch_amount", title="Number of Songs to Prefetch")
download_row.add(row)
# Max Concurrent Downloads
row = create_spin_button(0, 10, 1, "concurrent_download_limit", title="Maximum Concurrent Downloads")
download_row.add(row)
download_group.add(download_row)
download.add(download_group)
window.add(download)
self._show_transient_window(window)
def _show_transient_window(self, window):
window.set_transient_for(self)
if not self.is_desktop and self.is_maximized():

View File

@@ -106,11 +106,10 @@ class Desktop(Gtk.Box):
self.volume_mute_toggle.set_icon(f"audio-volume-{icon_name}-symbolic")
self.editing = True
self.volume_slider.set_value(
0 if app_config.state.is_muted else app_config.state.volume
)
self.editing = False
self.volume_slider.set_sensitive(not app_config.state.is_muted)
# Update the current song information.
# TODO (#126): add popup of bigger cover art photo here
@@ -176,10 +175,6 @@ class Desktop(Gtk.Box):
self.album_art.set_from_file(cover_art_filename)
self.album_art.set_loading(loading)
def on_volume_change(self, scale: Gtk.Scale):
if not self.editing:
self.emit("volume-change", scale.get_value())
def on_play_queue_open(self, *_):
if not self.get_child_visible():
self.play_queue_popover.popdown()
@@ -274,18 +269,11 @@ class Desktop(Gtk.Box):
orientation=Gtk.Orientation.HORIZONTAL,
adjustment=self.state.scrubber,
hexpand=True,
)
draw_value=False,
restrict_to_fill_level=False,
show_fill_level=True)
self.song_scrubber.set_name("song-scrubber")
self.song_scrubber.set_hexpand(True)
self.song_scrubber.set_draw_value(False)
self.song_scrubber.set_restrict_to_fill_level(False)
self.state.connect("notify::scrubber-cache", lambda *_: self.song_scrubber.set_fill_level(self.state.scrubber_cache))
# self.song_scrubber.set_name()
# self.song_scrubber.set_draw_value(False)
# self.song_scrubber.set_restrict_to_fill_level(False)
# self.song_scrubber.connect(
# "change-value", lambda s, t, v: self.emit("song-scrub", v)
# )
self.state.bind_property("scrubber-cache", self.song_scrubber, "fill-level")
scrubber_box.pack_start(self.song_scrubber, True, True, 0)
scrubber_box.pack_start(common.create_label(self.state, "duration-label"), False, False, 5)
@@ -396,7 +384,7 @@ class Desktop(Gtk.Box):
self.volume_mute_toggle = IconButton(
"audio-volume-high-symbolic", "Toggle mute"
)
self.volume_mute_toggle.set_action_name("app.mute-toggle")
self.volume_mute_toggle.set_action_name("app.toggle-mute")
box.pack_start(self.volume_mute_toggle, False, True, 0)
# Volume slider

View File

@@ -44,6 +44,9 @@ class Manager(GObject.GObject):
current_song_index = None
_updating = False
_updating_song_progress = False
def __init__(self, widget):
super().__init__()
@@ -109,6 +112,8 @@ class Manager(GObject.GObject):
self._controls.append(control)
def update(self, app_config: AppConfiguration, force: bool = False):
self._updating = True
self.volume.set_value(app_config.state.volume)
self.has_song = app_config.state.current_song is not None
@@ -147,17 +152,19 @@ class Manager(GObject.GObject):
self.shuffle_button_active = app_config.state.shuffle_on
self._updating = False
def update_song_progress(self,
progress: Optional[timedelta],
duration: Optional[timedelta],
cache_progess: Optional[timedelta]):
self.updating_scrubber = True
self._updating_song_progress = True
if progress is None or duration is None:
self.scrubber.set_value(0)
self.scrubber.set_upper(0)
self.scrubber_cache = 0
self.updating_scrubber = False
self._updating_song_progress = False
return
self.scrubber.set_value(progress.total_seconds())
@@ -167,7 +174,7 @@ class Manager(GObject.GObject):
else:
self.scrubber_cache = duration.total_seconds()
self.updating_scrubber = False
self._updating_song_progress = False
@util.async_callback(
partial(AdapterManager.get_cover_art_uri, scheme="file"),
@@ -411,6 +418,9 @@ class Manager(GObject.GObject):
self.device_list.show_all()
def on_volume_changed(self, _: Any):
if self._updating:
return
run_action(self.widget, 'app.set-volume', self.volume.get_value())
def on_scrubber_changed(self, _: Any):
@@ -420,7 +430,7 @@ class Manager(GObject.GObject):
self.progress_label = util.format_song_duration(
int(self.scrubber.get_value()))
if self.updating_scrubber:
if self._updating_song_progress:
return
run_action(self.widget, 'app.seek', self.scrubber.get_value())

View File

@@ -225,7 +225,8 @@ class MobileFlap(Gtk.Stack):
adjustment=self.state.scrubber,
name="song-scrubber",
draw_value=False,
restrict_to_fill_level=False)
restrict_to_fill_level=False,
show_fill_level=True)
self.state.bind_property("scrubber-cache", scrubber, "fill-level")
box.pack_start(scrubber, False, False, 0)