This commit is contained in:
Benjamin Schaaf
2021-02-03 18:00:06 +11:00
parent c612f31f42
commit d7d774c579
3 changed files with 319 additions and 258 deletions

View File

@@ -18,11 +18,13 @@ try:
except Exception:
tap_imported = False
import gi
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk
try:
import gi
gi.require_version('Handy', '1')
from gi.repository import Handy
try:
gi.require_version("Notify", "0.7")
from gi.repository import Notify
@@ -59,6 +61,8 @@ class SublimeMusicApp(Gtk.Application):
if glib_notify_exists:
Notify.init("Sublime Music")
Handy.init()
self.window: Optional[Gtk.Window] = None
self.app_config = AppConfiguration.load_from_file(config_file)
self.dbus_manager: Optional[DBusManager] = None

View File

@@ -4,7 +4,7 @@ import logging
import math
from typing import Any, Callable, cast, Iterable, List, Optional, Tuple
from gi.repository import Gdk, Gio, GLib, GObject, Gtk, Pango
from gi.repository import Gdk, Gio, GLib, GObject, Gtk, Pango, Handy
from ..adapters import (
AdapterManager,
@@ -71,6 +71,8 @@ class AlbumsPanel(Gtk.Box):
def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
leaflet = Handy.Leaflet(transition_type=Handy.LeafletTransitionType.SLIDE, can_swipe_forward=False)
actionbar = Gtk.ActionBar()
# Sort by
@@ -160,6 +162,7 @@ class AlbumsPanel(Gtk.Box):
actionbar.pack_end(Gtk.Label(label="Show"))
# self.add(actionbar)
leaflet.add(actionbar)
scrolled_window = Gtk.ScrolledWindow()
self.grid = AlbumsGrid()

View File

@@ -4,10 +4,6 @@ from typing import Any, Callable, Dict, Optional, Set, Tuple
import bleach
import gi
from gi.repository import GIRepository
GIRepository.Repository.prepend_search_path('/usr/local/lib/x86_64-linux-gnu/girepository-1.0')
gi.require_version('Handy', '1')
from gi.repository import Gdk, GLib, GObject, Gtk, Pango, Handy
from ..adapters import (
@@ -19,7 +15,7 @@ from ..adapters import (
from ..config import AppConfiguration, ProviderConfiguration
from ..players import PlayerManager
from ..ui import albums, artists, browse, player_controls, playlists, util
from ..ui.common import IconButton, IconMenuButton, SpinnerImage
from ..ui.common import IconButton, IconToggleButton, IconMenuButton, SpinnerImage
class MainWindow(Gtk.ApplicationWindow):
@@ -49,10 +45,27 @@ class MainWindow(Gtk.ApplicationWindow):
_pending_downloads_label: Optional[Gtk.Label] = None
_current_downloads_placeholder: Optional[Gtk.Label] = None
# Settings
setting_song_play_notification = GObject.Property(type=bool, default=False)
setting_allow_song_downloads = GObject.Property(type=bool, default=False)
setting_download_on_stream = GObject.Property(type=bool, default=False)
setting_prefetch_amount = GObject.Property(type=int, default=0)
setting_concurrent_download_limit = GObject.Property(type=int, default=0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.set_default_size(1342, 756)
def connect_setting(setting):
prop = setting.replace("_", "-")
self.connect(f"notify::setting-{prop}",
partial(self._on_settings_change, setting))
connect_setting("song_play_notification")
connect_setting("allow_song_downloads")
connect_setting("download_on_stream")
connect_setting("prefetch_amount")
connect_setting("concurrent_download_limit")
# Create the stack
self.albums_panel = albums.AlbumsPanel()
self.artists_panel = artists.ArtistsPanel()
@@ -60,16 +73,22 @@ class MainWindow(Gtk.ApplicationWindow):
self.playlists_panel = playlists.PlaylistsPanel()
self.stack = self._create_stack(
Albums=self.albums_panel,
# Artists=self.artists_panel,
# Browse=self.browse_panel,
# Playlists=self.playlists_panel,
Artists=self.artists_panel,
Browse=self.browse_panel,
Playlists=self.playlists_panel,
)
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.titlebar = self._create_headerbar(self.stack)
# self.set_titlebar(self.titlebar)
self.sidebar_flap = Handy.Flap(
orientation=Gtk.Orientation.HORIZONTAL,
fold_policy=Handy.FlapFoldPolicy.ALWAYS,
transition_type=Handy.FlapTransitionType.OVER,
modal=True)
flap = Handy.Flap(orientation=Gtk.Orientation.VERTICAL, fold_policy=Handy.FlapFoldPolicy.ALWAYS, flap_position=Gtk.PackType.END)
self.titlebar = self._create_headerbar(self.stack)
self.set_titlebar(self.titlebar)
drawer = Handy.Flap(orientation=Gtk.Orientation.VERTICAL, fold_policy=Handy.FlapFoldPolicy.ALWAYS, flap_position=Gtk.PackType.END)
notification_container = Gtk.Overlay()
notification_container.add(self.stack)
@@ -96,7 +115,7 @@ class MainWindow(Gtk.ApplicationWindow):
self.notification_revealer.add(notification_box)
notification_container.add_overlay(self.notification_revealer)
flap.set_content(notification_container)
drawer.set_content(notification_container)
# Player state
self.player_manager = player_controls.Manager()
@@ -112,7 +131,7 @@ class MainWindow(Gtk.ApplicationWindow):
)
# Player Controls
flap.set_separator(Gtk.Separator())
drawer.set_separator(Gtk.Separator())
squeezer = Handy.Squeezer(vexpand=True, homogeneous=False)
@@ -121,13 +140,13 @@ class MainWindow(Gtk.ApplicationWindow):
mobile_handle = player_controls.MobileHandle(self.player_manager)
# Toggle flap when handle is pressed
def toggle_flap(handle, event):
if event.get_click_count() != (True, 1) or not flap.get_swipe_to_open():
# Toggle drawer when handle is pressed
def toggle_drawer(handle, event):
if event.get_click_count() != (True, 1) or not drawer.get_swipe_to_open():
return
flap.set_reveal_flap(not flap.get_reveal_flap())
mobile_handle.connect("button-press-event", toggle_flap)
drawer.set_reveal_flap(not drawer.get_reveal_flap())
mobile_handle.connect("button-press-event", toggle_drawer)
squeezer.add(mobile_handle)
@@ -135,27 +154,31 @@ class MainWindow(Gtk.ApplicationWindow):
def squeezer_changed(squeezer, _):
is_desktop = squeezer.get_visible_child() == desktop_controls
flap.set_swipe_to_open(not is_desktop)
drawer.set_swipe_to_open(not is_desktop)
# When transitioning, don't play the reveal animation
dur = flap.get_reveal_duration()
flap.set_reveal_duration(0)
dur = drawer.get_reveal_duration()
drawer.set_reveal_duration(0)
flap.set_reveal_flap(False)
drawer.set_reveal_flap(False)
flap.set_reveal_duration(dur)
drawer.set_reveal_duration(dur)
squeezer.connect("notify::visible-child", squeezer_changed)
flap.set_handle(squeezer)
drawer.set_handle(squeezer)
mobile_flap = player_controls.MobileFlap(self.player_manager)
flap.set_flap(mobile_flap)
drawer.set_flap(mobile_flap)
def flap_reveal_progress(flap, _):
self.player_manager.flap_open = flap.get_reveal_progress() > 0.5
flap.connect("notify::reveal-progress", flap_reveal_progress)
def drawer_reveal_progress(drawer, _):
self.player_manager.flap_open = drawer.get_reveal_progress() > 0.5
drawer.connect("notify::reveal-progress", drawer_reveal_progress)
self.add(flap)
self.sidebar_flap.set_content(drawer)
self.sidebar_flap.set_flap(self._create_sidebar())
self.add(self.sidebar_flap)
self.connect("button-release-event", self._on_button_release)
@@ -254,156 +277,147 @@ class MainWindow(Gtk.ApplicationWindow):
self.provider_options_box.show_all()
# Main Settings
self.notification_switch.set_active(app_config.song_play_notification)
# Player settings
for c in self.player_settings_box.get_children():
self.player_settings_box.remove(c)
def emit_player_settings_change(
player_name: str, option_name: str, value_extraction_fn: Callable, *args
):
if self._updating_settings:
return
self.emit(
"refresh-window",
{
"__player_setting__": (
player_name,
option_name,
value_extraction_fn(*args),
)
},
False,
)
for player_name, options in player_manager.get_configuration_options().items():
self.player_settings_box.add(Gtk.Separator())
self.player_settings_box.add(
self._create_label(
f"{player_name} Settings", name="menu-settings-separator"
)
)
for option_name, descriptor in options.items():
setting_box = Gtk.Box()
setting_box.add(option_name_label := Gtk.Label(label=option_name))
option_name_label.get_style_context().add_class("menu-label")
option_value = app_config.player_config.get(player_name, {}).get(
option_name
)
if type(descriptor) == tuple:
option_store = Gtk.ListStore(str)
for option in descriptor:
option_store.append([option])
combo = Gtk.ComboBox.new_with_model(option_store)
combo.set_id_column(0)
renderer_text = Gtk.CellRendererText()
combo.pack_start(renderer_text, True)
combo.add_attribute(renderer_text, "text", 0)
combo.set_active_id(option_value)
combo.connect(
"changed",
partial(
emit_player_settings_change,
player_name,
option_name,
lambda c: c.get_active_id(),
),
)
setting_box.pack_end(combo, False, False, 0)
elif descriptor == bool:
switch = Gtk.Switch(active=option_value)
switch.connect(
"notify::active",
partial(
emit_player_settings_change,
player_name,
option_name,
lambda s, _: s.get_active(),
),
)
setting_box.pack_end(switch, False, False, 0)
elif descriptor == int:
int_editor_box = Gtk.Box()
def restrict_to_ints(
entry: Gtk.Entry, text: str, length: int, position: int
) -> bool:
if self._updating_settings:
return False
if not text.isdigit():
entry.emit_stop_by_name("insert-text")
return True
return False
entry = Gtk.Entry(width_chars=8, text=option_value, sensitive=False)
entry.connect("insert-text", restrict_to_ints)
int_editor_box.add(entry)
buttons_box = Gtk.Box()
edit_button = IconButton("document-edit-symbolic", relief=True)
confirm_button = IconButton("object-select-symbolic", relief=True)
cancel_button = IconButton("process-stop-symbolic", relief=True)
def on_edit_button_click(*a):
entry.set_sensitive(True)
buttons_box.remove(edit_button)
buttons_box.add(cancel_button)
buttons_box.add(confirm_button)
buttons_box.show_all()
def on_cancel_button_click(*a):
entry.set_text(str(option_value))
entry.set_sensitive(False)
buttons_box.remove(cancel_button)
buttons_box.remove(confirm_button)
buttons_box.add(edit_button)
buttons_box.show_all()
edit_button.connect("clicked", on_edit_button_click)
confirm_button.connect(
"clicked",
partial(
emit_player_settings_change,
player_name,
option_name,
lambda b: int(entry.get_text()),
),
)
cancel_button.connect("clicked", on_cancel_button_click)
buttons_box.add(edit_button)
int_editor_box.add(buttons_box)
setting_box.pack_end(int_editor_box, False, False, 0)
setting_box.get_style_context().add_class("menu-button")
self.player_settings_box.add(setting_box)
self.player_settings_box.show_all()
# Download Settings
allow_song_downloads = app_config.allow_song_downloads
self.allow_song_downloads_switch.set_active(allow_song_downloads)
self.download_on_stream_switch.set_active(app_config.download_on_stream)
self.prefetch_songs_entry.set_value(app_config.prefetch_amount)
self.max_concurrent_downloads_entry.set_value(
app_config.concurrent_download_limit
)
self.download_on_stream_switch.set_sensitive(allow_song_downloads)
self.prefetch_songs_entry.set_sensitive(allow_song_downloads)
self.max_concurrent_downloads_entry.set_sensitive(allow_song_downloads)
self.setting_song_play_notification = app_config.song_play_notification
self.setting_allow_song_downloads = app_config.allow_song_downloads
self.setting_download_on_stream = app_config.download_on_stream
self.setting_prefetch_amount = app_config.prefetch_amount
self.setting_concurrent_download_limit = app_config.concurrent_download_limit
self._updating_settings = False
# Player settings
# for c in self.player_settings_box.get_children():
# self.player_settings_box.remove(c)
# def emit_player_settings_change(
# player_name: str, option_name: str, value_extraction_fn: Callable, *args
# ):
# if self._updating_settings:
# return
# self.emit(
# "refresh-window",
# {
# "__player_setting__": (
# player_name,
# option_name,
# value_extraction_fn(*args),
# )
# },
# False,
# )
# for player_name, options in player_manager.get_configuration_options().items():
# self.player_settings_box.add(Gtk.Separator())
# self.player_settings_box.add(
# self._create_label(
# f"{player_name} Settings", name="menu-settings-separator"
# )
# )
# for option_name, descriptor in options.items():
# setting_box = Gtk.Box()
# setting_box.add(option_name_label := Gtk.Label(label=option_name))
# option_name_label.get_style_context().add_class("menu-label")
# option_value = app_config.player_config.get(player_name, {}).get(
# option_name
# )
# if type(descriptor) == tuple:
# option_store = Gtk.ListStore(str)
# for option in descriptor:
# option_store.append([option])
# combo = Gtk.ComboBox.new_with_model(option_store)
# combo.set_id_column(0)
# renderer_text = Gtk.CellRendererText()
# combo.pack_start(renderer_text, True)
# combo.add_attribute(renderer_text, "text", 0)
# combo.set_active_id(option_value)
# combo.connect(
# "changed",
# partial(
# emit_player_settings_change,
# player_name,
# option_name,
# lambda c: c.get_active_id(),
# ),
# )
# setting_box.pack_end(combo, False, False, 0)
# elif descriptor == bool:
# switch = Gtk.Switch(active=option_value)
# switch.connect(
# "notify::active",
# partial(
# emit_player_settings_change,
# player_name,
# option_name,
# lambda s, _: s.get_active(),
# ),
# )
# setting_box.pack_end(switch, False, False, 0)
# elif descriptor == int:
# int_editor_box = Gtk.Box()
# def restrict_to_ints(
# entry: Gtk.Entry, text: str, length: int, position: int
# ) -> bool:
# if self._updating_settings:
# return False
# if not text.isdigit():
# entry.emit_stop_by_name("insert-text")
# return True
# return False
# entry = Gtk.Entry(width_chars=8, text=option_value, sensitive=False)
# entry.connect("insert-text", restrict_to_ints)
# int_editor_box.add(entry)
# buttons_box = Gtk.Box()
# edit_button = IconButton("document-edit-symbolic", relief=True)
# confirm_button = IconButton("object-select-symbolic", relief=True)
# cancel_button = IconButton("process-stop-symbolic", relief=True)
# def on_edit_button_click(*a):
# entry.set_sensitive(True)
# buttons_box.remove(edit_button)
# buttons_box.add(cancel_button)
# buttons_box.add(confirm_button)
# buttons_box.show_all()
# def on_cancel_button_click(*a):
# entry.set_text(str(option_value))
# entry.set_sensitive(False)
# buttons_box.remove(cancel_button)
# buttons_box.remove(confirm_button)
# buttons_box.add(edit_button)
# buttons_box.show_all()
# edit_button.connect("clicked", on_edit_button_click)
# confirm_button.connect(
# "clicked",
# partial(
# emit_player_settings_change,
# player_name,
# option_name,
# lambda b: int(entry.get_text()),
# ),
# )
# cancel_button.connect("clicked", on_cancel_button_click)
# buttons_box.add(edit_button)
# int_editor_box.add(buttons_box)
# setting_box.pack_end(int_editor_box, False, False, 0)
# setting_box.get_style_context().add_class("menu-button")
# self.player_settings_box.add(setting_box)
# self.player_settings_box.show_all()
self.stack.set_visible_child_name(app_config.state.current_tab)
active_panel = self.stack.get_visible_child()
@@ -556,7 +570,7 @@ class MainWindow(Gtk.ApplicationWindow):
)
def _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack:
stack = Gtk.Stack()
stack = Gtk.Stack(homogeneous=True)
for name, child in kwargs.items():
child.connect(
"song-clicked",
@@ -573,9 +587,12 @@ class MainWindow(Gtk.ApplicationWindow):
"""
Configure the header bar for the window.
"""
header = Handy.HeaderBar()
header.set_show_close_button(True)
header.props.title = "Sublime Music"
squeezer = Handy.Squeezer()
# Desktop header
desktop_header = Handy.HeaderBar()
desktop_header.set_show_close_button(True)
desktop_header.props.title = "Sublime Music"
# Search
self.search_entry = Gtk.SearchEntry(placeholder_text="Search everything...")
@@ -586,14 +603,14 @@ class MainWindow(Gtk.ApplicationWindow):
self.search_entry.connect("focus-out-event", self._on_search_entry_loose_focus)
self.search_entry.connect("changed", self._on_search_entry_changed)
self.search_entry.connect("stop-search", self._on_search_entry_stop_search)
header.pack_start(self.search_entry)
# desktop_header.pack_start(self.search_entry)
# Search popup
self._create_search_popup()
# Stack switcher
switcher = Gtk.StackSwitcher(stack=stack)
header.set_custom_title(switcher)
desktop_header.set_custom_title(switcher)
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
@@ -608,16 +625,14 @@ class MainWindow(Gtk.ApplicationWindow):
self.downloads_popover.set_relative_to(self.downloads_menu_button)
button_box.add(self.downloads_menu_button)
# Menu button
self.main_menu_popover = self._create_main_menu()
main_menu_button = IconMenuButton(
# Preferences
preferences_button = IconButton(
"emblem-system-symbolic",
tooltip_text="Open Sublime Music settings",
popover=self.main_menu_popover,
relief=True,
)
main_menu_button.connect("clicked", self._on_main_menu_clicked)
self.main_menu_popover.set_relative_to(main_menu_button)
button_box.add(main_menu_button)
preferences_button.connect("clicked", self._show_settings)
button_box.add(preferences_button)
# Server icon and change server dropdown
self.server_connection_popover = self._create_server_connection_popover()
@@ -634,9 +649,56 @@ class MainWindow(Gtk.ApplicationWindow):
)
button_box.add(self.server_connection_menu_button)
header.pack_end(button_box)
desktop_header.pack_end(button_box)
return header
squeezer.add(desktop_header)
# Mobile header
mobile_header = Handy.HeaderBar()
mobile_header.set_show_close_button(True)
button = IconToggleButton("open-menu-symbolic")
self.sidebar_flap.bind_property("reveal-flap", button, "active", GObject.BindingFlags.BIDIRECTIONAL)
mobile_header.pack_start(button)
squeezer.add(mobile_header)
def squeezer_changed(squeezer, _):
is_desktop = squeezer.get_visible_child() == desktop_header
self.sidebar_flap.set_swipe_to_open(not is_desktop)
# When transitioning, don't play the reveal animation
dur = self.sidebar_flap.get_reveal_duration()
self.sidebar_flap.set_reveal_duration(0)
self.sidebar_flap.set_reveal_flap(False)
self.sidebar_flap.set_reveal_duration(dur)
squeezer.connect("notify::visible-child", squeezer_changed)
return squeezer
def _create_sidebar(self):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, width_request=150)
box.get_style_context().add_class("background")
stack = Gtk.StackSidebar(stack=self.stack, vexpand=True)
box.pack_start(stack, True, True, 0)
box.pack_end(Gtk.Separator(), False, False, 0)
servers = IconButton("list-add-symbolic", label="Servers")
box.pack_end(servers, 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")
box.pack_end(downloads, False, False, 0)
return box
def _create_label(
self, text: str, *args, halign: Gtk.Align = Gtk.Align.START, **kwargs
@@ -712,23 +774,6 @@ class MainWindow(Gtk.ApplicationWindow):
return box
def _create_spin_button_menu_item(
self, label: str, low: int, high: int, step: int, settings_name: str
) -> Tuple[Gtk.Box, Gtk.Entry]:
def on_change(entry: Gtk.SpinButton) -> bool:
self._emit_settings_change({settings_name: int(entry.get_value())})
return False
box = Gtk.Box()
box.add(spin_button_label := Gtk.Label(label=label))
spin_button_label.get_style_context().add_class("menu-label")
entry = Gtk.SpinButton.new_with_range(low, high, step)
entry.connect("value-changed", on_change)
box.pack_end(entry, False, False, 0)
box.get_style_context().add_class("menu-button")
return box, entry
def _create_downloads_popover(self) -> Gtk.PopoverMenu:
menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="downloads-menu")
@@ -860,66 +905,74 @@ class MainWindow(Gtk.ApplicationWindow):
menu.child_set_property(switch_provider_options, "submenu", "switch-provider")
return menu
def _create_main_menu(self) -> Gtk.PopoverMenu:
main_menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="main-menu-box")
def _create_settings_window(self) -> Gtk.PopoverMenu:
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
notifications_box, self.notification_switch = self._create_toggle_menu_button(
"Enable Song Notifications", "song_play_notification"
)
vbox.add(notifications_box)
row = create_switch("song_play_notification", title="Enable Song Notifications")
general_group.add(row)
# PLAYER SETTINGS
# ==============================================================================
# Player settings
self.player_settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
vbox.add(self.player_settings_box)
# vbox.add(self.player_settings_box)
general.add(general_group)
window.add(general)
# DOWNLOAD SETTINGS
# ==============================================================================
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
vbox.add(
self._create_label("Download Settings", name="menu-settings-separator")
)
download = Handy.PreferencesPage(icon_name="folder-download-symbolic", title="Downloads")
download_group = Handy.PreferencesGroup(title="Downloads", description="Settings for downloads")
# Allow Song Downloads
(
allow_song_downloads,
self.allow_song_downloads_switch,
) = self._create_toggle_menu_button(
"Allow Song Downloads", "allow_song_downloads"
)
vbox.add(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
(
download_on_stream,
self.download_on_stream_switch,
) = self._create_toggle_menu_button(
"When Streaming, Also Download Song", "download_on_stream"
)
vbox.add(download_on_stream)
row = create_switch("download_on_stream", title="When Streaming, Also Download Song")
download_row.add(row)
# Prefetch Songs
(
prefetch_songs_box,
self.prefetch_songs_entry,
) = self._create_spin_button_menu_item(
"Number of Songs to Prefetch", 0, 10, 1, "prefetch_amount"
)
vbox.add(prefetch_songs_box)
row = create_spin_button(0, 10, 1, "prefetch_amount", title="Number of Songs to Prefetch")
download_row.add(row)
# Max Concurrent Downloads
(
max_concurrent_downloads,
self.max_concurrent_downloads_entry,
) = self._create_spin_button_menu_item(
"Maximum Concurrent Downloads", 0, 10, 1, "concurrent_download_limit"
)
vbox.add(max_concurrent_downloads)
row = create_spin_button(0, 10, 1, "concurrent_download_limit", title="Maximum Concurrent Downloads")
download_row.add(row)
main_menu.add(vbox)
return main_menu
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
self._emit_settings_change({setting: self.get_property(prop.name)})
def _create_search_popup(self) -> Gtk.PopoverMenu:
self.search_popup = Gtk.PopoverMenu(modal=False)
@@ -1032,9 +1085,10 @@ class MainWindow(Gtk.ApplicationWindow):
self.server_connection_popover.popup()
self.server_connection_popover.show_all()
def _on_main_menu_clicked(self, *args):
self.main_menu_popover.popup()
self.main_menu_popover.show_all()
def _show_settings(self, *args):
window = self._create_settings_window()
window.set_transient_for(self)
window.show_all()
def _on_search_entry_focus(self, *args):
self._show_search()