Major GTK refactoring to use GTK Application and actions

This commit is contained in:
Sumner Evans
2019-06-04 20:38:31 -06:00
parent 0177b3ade2
commit 4a61a71ed8
10 changed files with 110 additions and 60 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -1 +1,4 @@
"""
This module defines a stateless server which interops with the Subsonic API.
"""
from .server import Server

View File

@@ -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())

View File

@@ -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:
"""

View File

@@ -1 +1 @@
from .main import MainWindow
from .app import LibremsonicApp

View File

@@ -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
View 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()

View 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()

View File

@@ -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