Major GTK refactoring to use GTK Application and actions
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
libremsonic
|
||||
===========
|
||||
|
||||
A \*sonic client for the Librem 5.
|
||||
A \*sonic client for the Linux Desktop.
|
||||
|
||||
Built using Python and GTK+.
|
||||
|
||||
Design Decisions
|
||||
================
|
||||
|
||||
- The ``server`` module is stateless. The only thing that it does is allow the
|
||||
module's user to query the Airsonic server via the API.
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#! /usr/bin/env python3
|
||||
import asyncio
|
||||
import threading
|
||||
import argparse
|
||||
import sys
|
||||
@@ -8,17 +7,9 @@ import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GLib, Gio, Gtk
|
||||
|
||||
from .ui import MainWindow
|
||||
from .server import Server
|
||||
from .ui import LibremsonicApp
|
||||
|
||||
|
||||
def main():
|
||||
server = Server(name='ohea',
|
||||
hostname='https://airsonic.the-evans.family',
|
||||
username=sys.argv[1],
|
||||
password=sys.argv[2])
|
||||
|
||||
win = MainWindow(server)
|
||||
win.connect("destroy", Gtk.main_quit)
|
||||
win.show_all()
|
||||
Gtk.main()
|
||||
app = LibremsonicApp()
|
||||
app.run(sys.argv)
|
||||
|
@@ -1 +1,4 @@
|
||||
"""
|
||||
This module defines a stateless server which interops with the Subsonic API.
|
||||
"""
|
||||
from .server import Server
|
||||
|
@@ -6,11 +6,18 @@ from dateutil import parser
|
||||
|
||||
def _from_json(cls, data):
|
||||
"""
|
||||
Approach for deserialization here:
|
||||
https://stackoverflow.com/a/40639688/2319844
|
||||
Converts data from a JSON parse into Python data structures.
|
||||
|
||||
Arguments:
|
||||
|
||||
cls: the template class to deserialize into
|
||||
data: the data to deserialize to the class
|
||||
"""
|
||||
# Approach for deserialization here:
|
||||
# https://stackoverflow.com/a/40639688/2319844
|
||||
|
||||
# If it's a forward reference, evaluate it to figure out the actual
|
||||
# type.
|
||||
# type. This allows for types that have to be put into a string.
|
||||
if isinstance(cls, typing.ForwardRef):
|
||||
cls = cls._evaluate(globals(), locals())
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import requests
|
||||
from asyncio import sleep
|
||||
from typing import List, Optional, Dict, Awaitable
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
from .api_objects import (SubsonicResponse, License, MusicFolder, Indexes,
|
||||
AlbumInfo, ArtistInfo, VideoInfo, File, Album,
|
||||
@@ -34,7 +33,7 @@ class Server:
|
||||
def _make_url(self, endpoint: str) -> str:
|
||||
return f'{self.hostname}/rest/{endpoint}.view'
|
||||
|
||||
async def _post(self, url, **params) -> SubsonicResponse:
|
||||
def _post(self, url, **params) -> SubsonicResponse:
|
||||
"""
|
||||
Make a post to a *Sonic REST API. Handle all types of errors including
|
||||
*Sonic ``<error>`` responses.
|
||||
@@ -55,6 +54,8 @@ class Server:
|
||||
if not subsonic_response:
|
||||
raise Exception('Fail!')
|
||||
|
||||
# Debug
|
||||
# TODO: logging
|
||||
print(subsonic_response)
|
||||
|
||||
response = SubsonicResponse.from_json(subsonic_response)
|
||||
@@ -65,11 +66,11 @@ class Server:
|
||||
|
||||
return response
|
||||
|
||||
async def ping(self) -> SubsonicResponse:
|
||||
def ping(self) -> SubsonicResponse:
|
||||
"""
|
||||
Used to test connectivity with the server.
|
||||
"""
|
||||
return await self._post(self._make_url('ping'))
|
||||
return self._post(self._make_url('ping'))
|
||||
|
||||
def get_license(self) -> License:
|
||||
"""
|
||||
|
@@ -1 +1 @@
|
||||
from .main import MainWindow
|
||||
from .app import LibremsonicApp
|
||||
|
@@ -1,44 +1,16 @@
|
||||
import gi
|
||||
import sys
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk
|
||||
|
||||
|
||||
class AlbumsPanel(Gtk.Box):
|
||||
"""Defines the albums panel."""
|
||||
def __init__(self, server):
|
||||
|
||||
def __init__(self):
|
||||
Gtk.Container.__init__(self)
|
||||
|
||||
albums = Gtk.Label('Albums')
|
||||
|
||||
self.add(albums)
|
||||
|
||||
def create_stack(self, **kwargs):
|
||||
stack = Gtk.Stack()
|
||||
for name, child in kwargs.items():
|
||||
stack.add_titled(child, name.lower(), name)
|
||||
return stack
|
||||
|
||||
def create_headerbar(self, stack):
|
||||
"""
|
||||
Configure the header bar for the window.
|
||||
"""
|
||||
header = Gtk.HeaderBar()
|
||||
header.set_show_close_button(True)
|
||||
header.props.title = 'LibremSonic'
|
||||
|
||||
# Search
|
||||
search = Gtk.SearchEntry()
|
||||
header.pack_start(search)
|
||||
|
||||
# Stack switcher
|
||||
switcher = Gtk.StackSwitcher(stack=stack)
|
||||
header.set_custom_title(switcher)
|
||||
|
||||
# Menu button
|
||||
button = Gtk.MenuButton()
|
||||
icon = Gio.ThemedIcon(name="open-menu-symbolic")
|
||||
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
||||
button.add(image)
|
||||
header.pack_end(button)
|
||||
|
||||
return header
|
||||
|
47
libremsonic/ui/app.py
Normal file
47
libremsonic/ui/app.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk
|
||||
|
||||
from .main import MainWindow
|
||||
from .configure_servers import ConfigureServersDialog
|
||||
|
||||
|
||||
class LibremsonicApp(Gtk.Application):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(
|
||||
*args,
|
||||
application_id="com.sumnerevans.libremsonic",
|
||||
**kwargs,
|
||||
)
|
||||
self.window = None
|
||||
|
||||
# TODO load this from the config file
|
||||
self.current_server = None
|
||||
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
action = Gio.SimpleAction.new('configure_servers', None)
|
||||
action.connect('activate', self.on_configure_servers)
|
||||
self.add_action(action)
|
||||
|
||||
def do_activate(self):
|
||||
# We only allow a single window and raise any existing ones
|
||||
if not self.window:
|
||||
# Windows are associated with the application
|
||||
# when the last one is closed the application shuts down
|
||||
self.window = MainWindow(application=self, title="LibremSonic")
|
||||
|
||||
self.window.show_all()
|
||||
self.window.present()
|
||||
|
||||
if not self.current_server:
|
||||
self.show_configure_servers_dialog()
|
||||
|
||||
def on_configure_servers(self, action, param):
|
||||
self.show_configure_servers_dialog()
|
||||
|
||||
def show_configure_servers_dialog(self):
|
||||
dialog = ConfigureServersDialog(self.window)
|
||||
dialog.run()
|
||||
dialog.destroy()
|
16
libremsonic/ui/configure_servers.py
Normal file
16
libremsonic/ui/configure_servers.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class ConfigureServersDialog(Gtk.Dialog):
|
||||
def __init__(self, parent):
|
||||
Gtk.Dialog.__init__(self, 'Configure Servers', parent, 0,
|
||||
('Done', Gtk.ResponseType.NONE))
|
||||
|
||||
self.set_default_size(400, 400)
|
||||
label = Gtk.Label('ohea')
|
||||
|
||||
box = self.get_content_area()
|
||||
box.add(label)
|
||||
self.show_all()
|
@@ -5,11 +5,11 @@ from gi.repository import Gio, Gtk
|
||||
from .albums import AlbumsPanel
|
||||
|
||||
|
||||
class MainWindow(Gtk.Window):
|
||||
class MainWindow(Gtk.ApplicationWindow):
|
||||
"""Defines the main window for LibremSonic."""
|
||||
|
||||
def __init__(self, server):
|
||||
Gtk.Window.__init__(self, title="LibremSonic")
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_default_size(400, 200)
|
||||
|
||||
artists = Gtk.Label('Artists')
|
||||
@@ -18,14 +18,15 @@ class MainWindow(Gtk.Window):
|
||||
|
||||
# Create the stack
|
||||
stack = self.create_stack(
|
||||
Albums=AlbumsPanel(server),
|
||||
Albums=AlbumsPanel(),
|
||||
Artists=artists,
|
||||
Playlists=playlists,
|
||||
More=more,
|
||||
)
|
||||
stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
||||
|
||||
self.set_titlebar(self.create_headerbar(stack))
|
||||
titlebar = self.create_headerbar(stack)
|
||||
self.set_titlebar(titlebar)
|
||||
self.add(stack)
|
||||
|
||||
def create_stack(self, **kwargs):
|
||||
@@ -67,8 +68,14 @@ class MainWindow(Gtk.Window):
|
||||
def create_menu(self):
|
||||
self.menu = Gtk.PopoverMenu()
|
||||
|
||||
menu_items = [
|
||||
('app.configure_servers', Gtk.ModelButton('Configure Servers')),
|
||||
]
|
||||
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
vbox.pack_start(Gtk.Label('Foo'), False, True, 10)
|
||||
for name, item in menu_items:
|
||||
item.set_action_name(name)
|
||||
vbox.pack_start(item, False, True, 10)
|
||||
self.menu.add(vbox)
|
||||
|
||||
return self.menu
|
||||
|
Reference in New Issue
Block a user