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

View File

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

View File

@@ -4,10 +4,6 @@ from typing import Any, Callable, Dict, Optional, Set, Tuple
import bleach 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 gi.repository import Gdk, GLib, GObject, Gtk, Pango, Handy
from ..adapters import ( from ..adapters import (
@@ -19,7 +15,7 @@ from ..adapters import (
from ..config import AppConfiguration, ProviderConfiguration from ..config import AppConfiguration, ProviderConfiguration
from ..players import PlayerManager from ..players import PlayerManager
from ..ui import albums, artists, browse, player_controls, playlists, util 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): class MainWindow(Gtk.ApplicationWindow):
@@ -49,10 +45,27 @@ class MainWindow(Gtk.ApplicationWindow):
_pending_downloads_label: Optional[Gtk.Label] = None _pending_downloads_label: Optional[Gtk.Label] = None
_current_downloads_placeholder: 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# self.set_default_size(1342, 756) # 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 # Create the stack
self.albums_panel = albums.AlbumsPanel() self.albums_panel = albums.AlbumsPanel()
self.artists_panel = artists.ArtistsPanel() self.artists_panel = artists.ArtistsPanel()
@@ -60,16 +73,22 @@ class MainWindow(Gtk.ApplicationWindow):
self.playlists_panel = playlists.PlaylistsPanel() self.playlists_panel = playlists.PlaylistsPanel()
self.stack = self._create_stack( self.stack = self._create_stack(
Albums=self.albums_panel, Albums=self.albums_panel,
# Artists=self.artists_panel, Artists=self.artists_panel,
# Browse=self.browse_panel, Browse=self.browse_panel,
# Playlists=self.playlists_panel, Playlists=self.playlists_panel,
) )
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.titlebar = self._create_headerbar(self.stack) self.sidebar_flap = Handy.Flap(
# self.set_titlebar(self.titlebar) 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 = Gtk.Overlay()
notification_container.add(self.stack) notification_container.add(self.stack)
@@ -96,7 +115,7 @@ class MainWindow(Gtk.ApplicationWindow):
self.notification_revealer.add(notification_box) self.notification_revealer.add(notification_box)
notification_container.add_overlay(self.notification_revealer) notification_container.add_overlay(self.notification_revealer)
flap.set_content(notification_container) drawer.set_content(notification_container)
# Player state # Player state
self.player_manager = player_controls.Manager() self.player_manager = player_controls.Manager()
@@ -112,7 +131,7 @@ class MainWindow(Gtk.ApplicationWindow):
) )
# Player Controls # Player Controls
flap.set_separator(Gtk.Separator()) drawer.set_separator(Gtk.Separator())
squeezer = Handy.Squeezer(vexpand=True, homogeneous=False) squeezer = Handy.Squeezer(vexpand=True, homogeneous=False)
@@ -121,13 +140,13 @@ class MainWindow(Gtk.ApplicationWindow):
mobile_handle = player_controls.MobileHandle(self.player_manager) mobile_handle = player_controls.MobileHandle(self.player_manager)
# Toggle flap when handle is pressed # Toggle drawer when handle is pressed
def toggle_flap(handle, event): def toggle_drawer(handle, event):
if event.get_click_count() != (True, 1) or not flap.get_swipe_to_open(): if event.get_click_count() != (True, 1) or not drawer.get_swipe_to_open():
return return
flap.set_reveal_flap(not flap.get_reveal_flap()) drawer.set_reveal_flap(not drawer.get_reveal_flap())
mobile_handle.connect("button-press-event", toggle_flap) mobile_handle.connect("button-press-event", toggle_drawer)
squeezer.add(mobile_handle) squeezer.add(mobile_handle)
@@ -135,27 +154,31 @@ class MainWindow(Gtk.ApplicationWindow):
def squeezer_changed(squeezer, _): def squeezer_changed(squeezer, _):
is_desktop = squeezer.get_visible_child() == desktop_controls 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 # When transitioning, don't play the reveal animation
dur = flap.get_reveal_duration() dur = drawer.get_reveal_duration()
flap.set_reveal_duration(0) 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) squeezer.connect("notify::visible-child", squeezer_changed)
flap.set_handle(squeezer) drawer.set_handle(squeezer)
mobile_flap = player_controls.MobileFlap(self.player_manager) mobile_flap = player_controls.MobileFlap(self.player_manager)
flap.set_flap(mobile_flap) drawer.set_flap(mobile_flap)
def flap_reveal_progress(flap, _): def drawer_reveal_progress(drawer, _):
self.player_manager.flap_open = flap.get_reveal_progress() > 0.5 self.player_manager.flap_open = drawer.get_reveal_progress() > 0.5
flap.connect("notify::reveal-progress", flap_reveal_progress) 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) self.connect("button-release-event", self._on_button_release)
@@ -254,156 +277,147 @@ class MainWindow(Gtk.ApplicationWindow):
self.provider_options_box.show_all() self.provider_options_box.show_all()
# Main Settings self.setting_song_play_notification = app_config.song_play_notification
self.notification_switch.set_active(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
# Player settings self.setting_prefetch_amount = app_config.prefetch_amount
for c in self.player_settings_box.get_children(): self.setting_concurrent_download_limit = app_config.concurrent_download_limit
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._updating_settings = False 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) self.stack.set_visible_child_name(app_config.state.current_tab)
active_panel = self.stack.get_visible_child() active_panel = self.stack.get_visible_child()
@@ -556,7 +570,7 @@ class MainWindow(Gtk.ApplicationWindow):
) )
def _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack: def _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack:
stack = Gtk.Stack() stack = Gtk.Stack(homogeneous=True)
for name, child in kwargs.items(): for name, child in kwargs.items():
child.connect( child.connect(
"song-clicked", "song-clicked",
@@ -573,9 +587,12 @@ class MainWindow(Gtk.ApplicationWindow):
""" """
Configure the header bar for the window. Configure the header bar for the window.
""" """
header = Handy.HeaderBar() squeezer = Handy.Squeezer()
header.set_show_close_button(True)
header.props.title = "Sublime Music" # Desktop header
desktop_header = Handy.HeaderBar()
desktop_header.set_show_close_button(True)
desktop_header.props.title = "Sublime Music"
# Search # Search
self.search_entry = Gtk.SearchEntry(placeholder_text="Search everything...") 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("focus-out-event", self._on_search_entry_loose_focus)
self.search_entry.connect("changed", self._on_search_entry_changed) self.search_entry.connect("changed", self._on_search_entry_changed)
self.search_entry.connect("stop-search", self._on_search_entry_stop_search) 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 # Search popup
self._create_search_popup() self._create_search_popup()
# Stack switcher # Stack switcher
switcher = Gtk.StackSwitcher(stack=stack) 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) 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) self.downloads_popover.set_relative_to(self.downloads_menu_button)
button_box.add(self.downloads_menu_button) button_box.add(self.downloads_menu_button)
# Menu button # Preferences
self.main_menu_popover = self._create_main_menu() preferences_button = IconButton(
main_menu_button = IconMenuButton(
"emblem-system-symbolic", "emblem-system-symbolic",
tooltip_text="Open Sublime Music settings", tooltip_text="Open Sublime Music settings",
popover=self.main_menu_popover, relief=True,
) )
main_menu_button.connect("clicked", self._on_main_menu_clicked) preferences_button.connect("clicked", self._show_settings)
self.main_menu_popover.set_relative_to(main_menu_button) button_box.add(preferences_button)
button_box.add(main_menu_button)
# Server icon and change server dropdown # Server icon and change server dropdown
self.server_connection_popover = self._create_server_connection_popover() 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) 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( def _create_label(
self, text: str, *args, halign: Gtk.Align = Gtk.Align.START, **kwargs self, text: str, *args, halign: Gtk.Align = Gtk.Align.START, **kwargs
@@ -712,23 +774,6 @@ class MainWindow(Gtk.ApplicationWindow):
return box 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: def _create_downloads_popover(self) -> Gtk.PopoverMenu:
menu = Gtk.PopoverMenu() menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="downloads-menu") 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") menu.child_set_property(switch_provider_options, "submenu", "switch-provider")
return menu return menu
def _create_main_menu(self) -> Gtk.PopoverMenu: def _create_settings_window(self) -> Gtk.PopoverMenu:
main_menu = Gtk.PopoverMenu() window = Handy.PreferencesWindow()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="main-menu-box")
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
notifications_box, self.notification_switch = self._create_toggle_menu_button( row = create_switch("song_play_notification", title="Enable Song Notifications")
"Enable Song Notifications", "song_play_notification" general_group.add(row)
)
vbox.add(notifications_box)
# PLAYER SETTINGS # Player settings
# ==============================================================================
self.player_settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 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 # DOWNLOAD SETTINGS
# ============================================================================== # ==============================================================================
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)) download = Handy.PreferencesPage(icon_name="folder-download-symbolic", title="Downloads")
vbox.add( download_group = Handy.PreferencesGroup(title="Downloads", description="Settings for downloads")
self._create_label("Download Settings", name="menu-settings-separator")
)
# Allow Song Downloads # Allow Song Downloads
( download_row = Handy.ExpanderRow(show_enable_switch=True, title="Allow Song Downloads", expanded=True)
allow_song_downloads, self.bind_property("setting-allow-song-downloads", download_row, "enable-expansion", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE)
self.allow_song_downloads_switch,
) = self._create_toggle_menu_button(
"Allow Song Downloads", "allow_song_downloads"
)
vbox.add(allow_song_downloads)
# Download on Stream # Download on Stream
( row = create_switch("download_on_stream", title="When Streaming, Also Download Song")
download_on_stream, download_row.add(row)
self.download_on_stream_switch,
) = self._create_toggle_menu_button(
"When Streaming, Also Download Song", "download_on_stream"
)
vbox.add(download_on_stream)
# Prefetch Songs # Prefetch Songs
( row = create_spin_button(0, 10, 1, "prefetch_amount", title="Number of Songs to Prefetch")
prefetch_songs_box, download_row.add(row)
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)
# Max Concurrent Downloads # Max Concurrent Downloads
( row = create_spin_button(0, 10, 1, "concurrent_download_limit", title="Maximum Concurrent Downloads")
max_concurrent_downloads, download_row.add(row)
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)
main_menu.add(vbox) download_group.add(download_row)
return main_menu 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: def _create_search_popup(self) -> Gtk.PopoverMenu:
self.search_popup = Gtk.PopoverMenu(modal=False) self.search_popup = Gtk.PopoverMenu(modal=False)
@@ -1032,9 +1085,10 @@ class MainWindow(Gtk.ApplicationWindow):
self.server_connection_popover.popup() self.server_connection_popover.popup()
self.server_connection_popover.show_all() self.server_connection_popover.show_all()
def _on_main_menu_clicked(self, *args): def _show_settings(self, *args):
self.main_menu_popover.popup() window = self._create_settings_window()
self.main_menu_popover.show_all() window.set_transient_for(self)
window.show_all()
def _on_search_entry_focus(self, *args): def _on_search_entry_focus(self, *args):
self._show_search() self._show_search()