Started moving settings into popovers

This commit is contained in:
Sumner Evans
2020-05-22 01:25:05 -06:00
parent 20b8896cb8
commit ced6eff98e
6 changed files with 294 additions and 51 deletions

View File

@@ -490,6 +490,12 @@ class SublimeMusicApp(Gtk.Application):
def on_refresh_window(
self, _, state_updates: Dict[str, Any], force: bool = False,
):
if settings := state_updates.get("__settings__"):
for k, v in settings.items():
print('SET', k, v)
setattr(self.app_config, k, v)
del state_updates["__settings__"]
for k, v in state_updates.items():
setattr(self.app_config.state, k, v)
self.update_window(force=force)

View File

@@ -72,29 +72,39 @@ class ServerConfiguration:
@dataclass
class AppConfiguration:
version: int = 3
cache_location: str = ""
filename: Optional[Path] = None
# Servers
servers: List[ServerConfiguration] = field(default_factory=list)
current_server_index: int = -1
cache_location: str = ""
# Global Settings
song_play_notification: bool = True
offline_mode: bool = False
serve_over_lan: bool = True
# TODO this should probably be moved to the cache adapter settings
max_cache_size_mb: int = -1 # -1 means unlimited
always_stream: bool = False # always stream instead of downloading songs
download_on_stream: bool = True # also download when streaming a song
song_play_notification: bool = True
prefetch_amount: int = 3
concurrent_download_limit: int = 5
port_number: int = 8282
version: int = 3
serve_over_lan: bool = True
replay_gain: ReplayGainType = ReplayGainType.NO
filename: Optional[Path] = None
@staticmethod
def load_from_file(filename: Path) -> "AppConfiguration":
args = {}
if filename.exists():
with open(filename, "r") as f:
field_names = {f.name for f in fields(AppConfiguration)}
args = yaml.load(f, Loader=yaml.CLoader).items()
args = dict(filter(lambda kv: kv[0] in field_names, args))
try:
if filename.exists():
with open(filename, "r") as f:
field_names = {f.name for f in fields(AppConfiguration)}
args = yaml.load(f, Loader=yaml.CLoader).items()
args = dict(filter(lambda kv: kv[0] in field_names, args))
except Exception:
pass
config = AppConfiguration(**args)
config.filename = filename

View File

@@ -1,7 +1,10 @@
/* ********** Main ********** */
#connected-to-label {
margin-top: 10px;
margin-bottom: 10px;
#server-connection-icon { /* TODO remove */
/* box-shadow: 0px 0px 3px green; */
}
#main-menu-box {
min-width: 230px;
}
#icon-button-box image {
@@ -14,6 +17,11 @@
margin-right: 3px;
}
#menu-item-download-settings,
#menu-item-clear-cache {
min-width: 230px;
}
/* ********** Playlist ********** */
#playlist-list-listbox row {
margin: 0;

View File

@@ -1,6 +1,6 @@
from .album_with_songs import AlbumWithSongs
from .edit_form_dialog import EditFormDialog
from .icon_button import IconButton, IconToggleButton
from .icon_button import IconButton, IconMenuButton, IconToggleButton
from .song_list_column import SongListColumn
from .spinner_image import SpinnerImage
@@ -8,6 +8,7 @@ __all__ = (
"AlbumWithSongs",
"EditFormDialog",
"IconButton",
"IconMenuButton",
"IconToggleButton",
"SongListColumn",
"SpinnerImage",

View File

@@ -1,6 +1,6 @@
from typing import Optional
from typing import Any, Optional
from gi.repository import Gtk
from gi.repository import GdkPixbuf, Gtk
class IconButton(Gtk.Button):
@@ -70,3 +70,51 @@ class IconToggleButton(Gtk.ToggleButton):
def set_active(self, active: bool):
super().set_active(active)
class IconMenuButton(Gtk.MenuButton):
def __init__(
self,
icon_name: Optional[str] = None,
icon_image_filename: Optional[str] = None,
tooltip_text: str = "",
relief: bool = False,
icon_size: Gtk.IconSize = Gtk.IconSize.BUTTON,
label: str = None,
popover: Any = None,
**kwargs,
):
Gtk.MenuButton.__init__(self, **kwargs)
if popover:
self.set_use_popover(True)
self.set_popover(popover)
self.icon_size = icon_size
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, name="icon-button-box")
self.image = Gtk.Image()
if icon_name:
self.image.set_from_icon_name(icon_name, self.icon_size)
box.add(self.image)
elif icon_image_filename:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
icon_image_filename, -1, 16, True,
)
self.image.set_from_pixbuf(pixbuf)
box.add(self.image)
if label is not None:
box.add(Gtk.Label(label=label))
if not relief:
self.props.relief = Gtk.ReliefStyle.NONE
self.add(box)
self.set_tooltip_text(tooltip_text)
def set_icon(self, icon_name: Optional[str]):
self.image.set_from_icon_name(icon_name, self.icon_size)
def set_from_file(self, icon_file: Optional[str]):
self.image.set_from_file(icon_file)

View File

@@ -1,12 +1,12 @@
from functools import partial
from typing import Any, Optional, Set
from typing import Any, Callable, Optional, Set
from gi.repository import Gdk, Gio, GLib, GObject, Gtk, Pango
from sublime.adapters import AdapterManager, api_objects as API, Result
from sublime.config import AppConfiguration
from sublime.config import AppConfiguration, ReplayGainType
from sublime.ui import albums, artists, browse, player_controls, playlists, util
from sublime.ui.common import IconButton, SpinnerImage
from sublime.ui.common import IconButton, IconMenuButton, SpinnerImage
class MainWindow(Gtk.ApplicationWindow):
@@ -28,6 +28,8 @@ class MainWindow(Gtk.ApplicationWindow):
"go-to": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str, str),),
}
_updating_settings: bool = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_default_size(1150, 768)
@@ -109,14 +111,24 @@ class MainWindow(Gtk.ApplicationWindow):
self.notification_revealer.set_reveal_child(False)
# Update the Connected to label on the popup menu.
if app_config.server:
self.connected_to_label.set_markup(
f"<b>Connected to {app_config.server.name}</b>"
)
else:
self.connected_to_label.set_markup(
'<span style="italic">Not Connected to a Server</span>'
)
# if app_config.server:
# self.connected_to_label.set_markup(
# f"<b>Connected to {app_config.server.name}</b>"
# )
# else:
# self.connected_to_label.set_markup(
# '<span style="italic">Not Connected to a Server</span>'
# )
self._updating_settings = True
self.offline_mode_switch.set_active(app_config.offline_mode)
self.notification_switch.set_active(app_config.song_play_notification)
self.replay_gain_options.set_active_id(app_config.replay_gain.as_string())
self.serve_over_lan_switch.set_active(app_config.serve_over_lan)
self.port_number_entry.set_value(app_config.port_number)
self._updating_settings = False
self.stack.set_visible_child_name(app_config.state.current_tab)
@@ -164,19 +176,47 @@ class MainWindow(Gtk.ApplicationWindow):
switcher = Gtk.StackSwitcher(stack=stack)
header.set_custom_title(switcher)
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
# Downloads
self.downloads_popover = self._create_downloads_popover()
self.downloads_menu_button = IconMenuButton(
"folder-download-symbolic",
tooltip_text="Show download status",
popover=self.downloads_popover,
)
self.downloads_menu_button.connect("clicked", self._on_downloads_menu_clicked)
self.downloads_popover.set_relative_to(self.downloads_menu_button)
button_box.add(self.downloads_menu_button)
# Menu button
menu_button = Gtk.MenuButton()
menu_button.set_tooltip_text("Open application menu")
menu_button.set_use_popover(True)
menu_button.set_popover(self._create_menu())
menu_button.connect("clicked", self._on_menu_clicked)
self.menu.set_relative_to(menu_button)
self.main_menu_popover = self._create_main_menu()
main_menu_button = IconMenuButton(
"emblem-system-symbolic",
tooltip_text="Open Sublime Music settings",
popover=self.main_menu_popover,
)
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)
icon = Gio.ThemedIcon(name="open-menu-symbolic")
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
menu_button.add(image)
# Server icon and change server dropdown
self.server_connection_popover = self._create_server_connection_popover()
self.server_connection_menu_button = IconMenuButton(
name="server-connection-icon",
icon_image_filename="/home/sumner/tmp/server-subsonic-symbolic.svg",
tooltip_text="Server connection settings",
popover=self.server_connection_popover,
)
self.server_connection_menu_button.connect(
"clicked", self._on_server_connection_menu_clicked
)
self.server_connection_popover.set_relative_to(
self.server_connection_menu_button
)
button_box.add(self.server_connection_menu_button)
header.pack_end(menu_button)
header.pack_end(button_box)
return header
@@ -192,29 +232,141 @@ class MainWindow(Gtk.ApplicationWindow):
label.get_style_context().add_class("search-result-row")
return label
def _create_menu(self) -> Gtk.PopoverMenu:
self.menu = Gtk.PopoverMenu()
def _create_downloads_popover(self) -> Gtk.PopoverMenu:
menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="main-menu-box")
self.connected_to_label = self._create_label("", name="connected-to-label")
self.connected_to_label.set_markup(
'<span style="italic">Not Connected to a Server</span>'
download_settings = Gtk.ModelButton(
text="Clear Cache", menu_name="clear-cache", name="menu-item-clear-cache",
)
download_settings.get_style_context().add_class("menu-button")
vbox.add(download_settings)
menu.add(vbox)
return menu
def _create_server_connection_popover(self) -> Gtk.PopoverMenu:
menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
menu.add(vbox)
return menu
def _create_main_menu(self) -> Gtk.PopoverMenu:
main_menu = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="main-menu-box")
# TODO
# Current Server
# current_server_box = Gtk.Box(name="connected-to-box")
# self.connected_to_label = self._create_label("")
# self.connected_to_label.set_markup(
# '<span style="italic">Not connected to any music source</span>'
# )
# current_server_box.add(self.connected_to_label)
# edit_button = IconButton("document-edit-symbolic", "Edit the current server")
# edit_button.connect("clicked", lambda _: print("edit")) # TODO
# current_server_box.pack_end(edit_button, False, False, 5)
# vbox.add(current_server_box)
# # Music Source
# switch_source_button = Gtk.ModelButton(
# text="Switch Music Source",
# menu_name="switch-source",
# name="menu-item-switch-source",
# )
# switch_source_button.get_style_context().add_class("menu-button")
# vbox.add(switch_source_button)
# vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
# Offline Mode
offline_box, self.offline_mode_switch = self._create_toggle_menu_button(
"Offline Mode", "offline_mode"
)
vbox.add(offline_box)
download_settings = Gtk.ModelButton(
text="Download Settings",
menu_name="download-settings",
name="menu-item-download-settings",
)
download_settings.get_style_context().add_class("menu-button")
vbox.add(download_settings)
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
# Notifications
notifications_box, self.notification_switch = self._create_toggle_menu_button(
"Notifications", "song_play_notification"
)
vbox.add(notifications_box)
# Replay Gain
replay_gain_box = Gtk.Box()
replay_gain_box.add(Gtk.Label(label="Replay Gain"))
replay_gain_option_store = Gtk.ListStore(str, str)
for id, option in (("no", "Disabled"), ("track", "Track"), ("album", "Album")):
replay_gain_option_store.append([id, option])
self.replay_gain_options = Gtk.ComboBox.new_with_model(replay_gain_option_store)
self.replay_gain_options.set_id_column(0)
renderer_text = Gtk.CellRendererText()
self.replay_gain_options.pack_start(renderer_text, True)
self.replay_gain_options.add_attribute(renderer_text, "text", 1)
self.replay_gain_options.connect("changed", self._on_replay_gain_change)
replay_gain_box.pack_end(self.replay_gain_options, False, False, 0)
replay_gain_box.get_style_context().add_class("menu-button")
vbox.add(replay_gain_box)
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
# Serve Local Files to Chromecast
serve_over_lan, self.serve_over_lan_switch = self._create_toggle_menu_button(
"Serve Local Files to Devices on the LAN", "serve_over_lan"
)
vbox.add(serve_over_lan)
# Server Port
server_port_box = Gtk.Box()
server_port_box.add(Gtk.Label(label="LAN Server Port Number"))
self.port_number_entry = Gtk.SpinButton.new_with_range(8000, 9000, 1)
server_port_box.pack_end(self.port_number_entry, False, False, 0)
server_port_box.get_style_context().add_class("menu-button")
vbox.add(server_port_box)
menu_items = [
(None, self.connected_to_label),
("app.configure-servers", Gtk.ModelButton(text="Configure Servers"),),
("app.configure-servers", Gtk.ModelButton(text="Configure Servers")),
("app.settings", Gtk.ModelButton(text="Settings")),
]
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
for name, item in menu_items:
if name:
item.set_action_name(name)
item.get_style_context().add_class("menu-button")
vbox.pack_start(item, False, True, 0)
self.menu.add(vbox)
return self.menu
main_menu.add(vbox)
return main_menu
def _create_toggle_menu_button(self, label: str, settings_name: str) -> Gtk.Box:
def on_active_change(toggle: Gtk.Switch, _):
self._emit_settings_change(**{settings_name: toggle.get_active()})
box = Gtk.Box()
box.add(Gtk.Label(label=label))
switch = Gtk.Switch(active=True)
switch.connect("notify::active", on_active_change)
box.pack_end(switch, False, False, 0)
box.get_style_context().add_class("menu-button")
return box, switch
def _create_search_popup(self) -> Gtk.PopoverMenu:
self.search_popup = Gtk.PopoverMenu(modal=False)
@@ -265,7 +417,7 @@ class MainWindow(Gtk.ApplicationWindow):
# Event Listeners
# =========================================================================
def _on_button_release(self, win: Any, event: Gdk.EventButton) -> bool:
if not self._event_in_widgets(event, self.search_entry, self.search_popup,):
if not self._event_in_widgets(event, self.search_entry, self.search_popup):
self._hide_search()
if not self._event_in_widgets(
@@ -284,9 +436,22 @@ class MainWindow(Gtk.ApplicationWindow):
return False
def _on_menu_clicked(self, *args):
self.menu.popup()
self.menu.show_all()
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 _on_main_menu_clicked(self, *args):
self.main_menu_popover.popup()
self.main_menu_popover.show_all()
def _on_replay_gain_change(self, combo: Gtk.ComboBox):
self._emit_settings_change(
replay_gain=ReplayGainType.from_string(combo.get_active_id())
)
def _on_search_entry_focus(self, *args):
self._show_search()
@@ -339,6 +504,11 @@ class MainWindow(Gtk.ApplicationWindow):
# Helper Functions
# =========================================================================
def _emit_settings_change(self, **kwargs):
if self._updating_settings:
return
self.emit("refresh-window", {"__settings__": kwargs}, False)
def _show_search(self):
self.search_entry.set_size_request(300, -1)
self.search_popup.show_all()