Made Browse tab fairly functional

This commit is contained in:
Sumner Evans
2020-01-18 13:14:44 -07:00
parent be16c4578e
commit 57012499b5
7 changed files with 177 additions and 75 deletions

View File

@@ -96,8 +96,7 @@ class SublimeMusicApp(Gtk.Application):
add_action('add-to-queue', self.on_add_to_queue, parameter_type='as')
add_action('go-to-album', self.on_go_to_album, parameter_type='s')
add_action('go-to-artist', self.on_go_to_artist, parameter_type='s')
add_action(
'browse-to-song', self.on_browse_to_song, parameter_type='s')
add_action('browse-to', self.browse_to, parameter_type='s')
add_action(
'go-to-playlist', self.on_go_to_playlist, parameter_type='s')
@@ -583,9 +582,9 @@ class SublimeMusicApp(Gtk.Application):
self.state.selected_artist_id = artist_id.get_string()
self.update_window()
def on_browse_to_song(self, action, song_id):
# Really, we want to browse to the song's parent.
def browse_to(self, action, item_id):
self.state.current_tab = 'browse'
self.state.selected_browse_element_id = item_id.get_string()
self.update_window()
def on_go_to_playlist(self, action, playlist_id):

View File

@@ -42,7 +42,6 @@ from .server.api_objects import (
# Non-ID3 versions
Artist,
ArtistInfo,
Directory,
# ID3 versions
@@ -314,7 +313,7 @@ class CacheManager(metaclass=Singleton):
('song_details', Child, dict),
# Non-ID3 caches
('music_directories', Child, 'dict-list'),
('music_directories', Directory, dict),
('indexes', Artist, list),
# ID3 caches
@@ -331,6 +330,7 @@ class CacheManager(metaclass=Singleton):
for x in meta_json.get(name, [])
]
elif default == dict:
print('dict', name)
self.cache[name] = {
id: type_name.from_json(x)
for id, x in meta_json.get(name, {}).items()
@@ -605,16 +605,16 @@ class CacheManager(metaclass=Singleton):
id,
before_download: Callable[[], None] = lambda: None,
force: bool = False,
) -> 'CacheManager.Result[Child]':
) -> 'CacheManager.Result[Directory]':
cache_name = 'music_directories'
if id in self.cache.get(cache_name, {}) and not force:
return CacheManager.Result.from_data(
self.cache[cache_name][id])
def after_download(artist):
def after_download(album):
with self.cache_lock:
self.cache[cache_name][id] = artist
self.cache[cache_name][id] = album
self.save_cache_info()
return CacheManager.Result.from_server(
@@ -957,8 +957,7 @@ class CacheManager(metaclass=Singleton):
# Local Results
search_result = SearchResult(query)
search_result.add_results(
'album',
itertools.chain(*self.cache['albums'].values()))
'album', itertools.chain(*self.cache['albums'].values()))
search_result.add_results('artist', self.cache['artists'])
search_result.add_results(
'song', self.cache['song_details'].values())

View File

@@ -68,6 +68,7 @@ class ApplicationState:
current_tab: str = 'albums'
selected_album_id: str = None
selected_artist_id: str = None
selected_browse_element_id: str = None
selected_playlist_id: str = None
# State for Album sort.
@@ -111,6 +112,8 @@ class ApplicationState:
self.current_tab = json_object.get('current_tab', 'albums')
self.selected_album_id = json_object.get('selected_album_id', None)
self.selected_artist_id = json_object.get('selected_artist_id', None)
self.selected_browse_element_id = json_object.get(
'selected_browse_element_id', None)
self.selected_playlist_id = json_object.get(
'selected_playlist_id', None)
self.current_album_sort = json_object.get(

View File

@@ -22,7 +22,7 @@
#playlist-list-spinner:checked,
#artist-list-spinner:checked,
#directory-list-spinner:checked {
#drilldown-list-spinner:checked {
margin: 10px;
padding: 0px;
}

View File

@@ -77,7 +77,10 @@ class ArtistList(Gtk.Box):
self.add(list_actions)
self.loading_indicator = Gtk.ListBox()
spinner_row = Gtk.ListBoxRow()
spinner_row = Gtk.ListBoxRow(
activatable=False,
selectable=False,
)
spinner = Gtk.Spinner(
name='artist-list-spinner',
active=True,
@@ -216,8 +219,7 @@ class ArtistDetailPanel(Gtk.Box):
artist_details_box.add(self.artist_bio)
self.similar_artists_scrolledwindow = Gtk.ScrolledWindow()
similar_artists_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL)
similar_artists_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.similar_artists_label = self.make_label(name='similar-artists')
similar_artists_box.add(self.similar_artists_label)

View File

@@ -1,3 +1,5 @@
from typing import Union
import gi
gi.require_version('Gtk', '3.0')
@@ -8,8 +10,10 @@ from sublime.cache_manager import CacheManager
from sublime.ui import util
from sublime.ui.common import IconButton
from sublime.server.api_objects import Child, Artist
class BrowsePanel(Gtk.ScrolledWindow):
class BrowsePanel(Gtk.Overlay):
"""Defines the arist panel."""
__gsignals__ = {
@@ -25,18 +29,39 @@ class BrowsePanel(Gtk.ScrolledWindow):
),
}
id_stack = None
def __init__(self):
super().__init__()
scrolled_window = Gtk.ScrolledWindow()
self.root_directory_listing = ListAndDrilldown(IndexList)
scrolled_window.add(self.root_directory_listing)
self.add(scrolled_window)
self.root_directory_listing = DirectoryListAndDrilldown(is_root=True)
self.add(self.root_directory_listing)
self.spinner = Gtk.Spinner(
active=True,
halign=Gtk.Align.CENTER,
valign=Gtk.Align.CENTER,
)
self.add_overlay(self.spinner)
def update(self, state: ApplicationState, force=False):
self.root_directory_listing.update(state=state, force=force)
id_stack = []
# TODO make async
if CacheManager.ready:
directory = None
current_dir_id = state.selected_browse_element_id
while directory is None or directory.parent is not None:
directory = CacheManager.get_music_directory(
current_dir_id).result()
id_stack.append(directory.id)
current_dir_id = directory.parent
self.root_directory_listing.update(id_stack, state=state, force=force)
self.spinner.hide()
class DirectoryListAndDrilldown(Gtk.Paned):
class ListAndDrilldown(Gtk.Paned):
__gsignals__ = {
'song-clicked': (
GObject.SignalFlags.RUN_FIRST,
@@ -50,33 +75,60 @@ class DirectoryListAndDrilldown(Gtk.Paned):
),
}
def __init__(self, is_root=False):
id_stack = None
def __init__(self, list_type):
Gtk.Paned.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.is_root = is_root
self.directory_listing = DirectoryList()
self.pack1(self.directory_listing, False, False)
self.list = list_type()
self.pack1(self.list, False, False)
self.listing_drilldown_panel = Gtk.Box()
self.pack2(self.listing_drilldown_panel, True, False)
self.drilldown = Gtk.Box()
self.pack2(self.drilldown, True, False)
def update(self, state: ApplicationState, force=False):
self.directory_listing.update(
def update(
self,
id_stack,
state: ApplicationState,
force=False,
directory_id=None,
):
if self.id_stack == id_stack:
return
self.id_stack = id_stack
if len(id_stack) > 0:
self.remove(self.drilldown)
self.drilldown = ListAndDrilldown(MusicDirectoryList)
self.drilldown.update(
id_stack[:-1],
state,
force=force,
directory_id=id_stack[-1],
)
self.drilldown.show_all()
self.pack2(self.drilldown, True, False)
self.list.update(
None if len(id_stack) == 0 else id_stack[-1],
state=state,
force=force,
is_root=self.is_root,
directory_id=directory_id,
)
class DirectoryList(Gtk.Box):
class SubelementModel(GObject.GObject):
class DrilldownList(Gtk.Box):
class DrilldownElement(GObject.GObject):
id = GObject.Property(type=str)
name = GObject.Property(type=str)
is_dir = GObject.Property(type=bool, default=True)
def __init__(self, id, name):
def __init__(self, element: Union[Child, Artist]):
GObject.GObject.__init__(self)
self.id = id
self.name = name
self.id = element.id
self.name = (
element.name if isinstance(element, Artist) else element.title)
self.is_dir = isinstance(element, Artist) or element.isDir
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
@@ -84,15 +136,18 @@ class DirectoryList(Gtk.Box):
list_actions = Gtk.ActionBar()
refresh = IconButton('view-refresh-symbolic')
refresh.connect('clicked', lambda *a: self.update(force=True))
refresh.connect('clicked', self.on_refresh_clicked)
list_actions.pack_end(refresh)
self.add(list_actions)
self.loading_indicator = Gtk.ListBox()
spinner_row = Gtk.ListBoxRow()
spinner_row = Gtk.ListBoxRow(
activatable=False,
selectable=False,
)
spinner = Gtk.Spinner(
name='directory-list-spinner',
name='drilldown-list-spinner',
active=True,
)
spinner_row.add(spinner)
@@ -101,45 +156,22 @@ class DirectoryList(Gtk.Box):
list_scroll_window = Gtk.ScrolledWindow(min_content_width=250)
def create_row(model: DirectoryList.SubelementModel):
return Gtk.Label(
label=f'<b>{util.esc(model.name)}</b>',
use_markup=True,
margin=10,
halign=Gtk.Align.START,
ellipsize=Pango.EllipsizeMode.END,
max_width_chars=30,
)
self.directory_list_store = Gio.ListStore()
self.list = Gtk.ListBox(name='directory-list')
self.list.bind_model(self.directory_list_store, create_row)
self.drilldown_list_store = Gio.ListStore()
self.list = Gtk.ListBox()
self.list.bind_model(self.drilldown_list_store, self.create_row)
list_scroll_window.add(self.list)
self.pack_start(list_scroll_window, True, True, 0)
def update(
self,
state: ApplicationState = None,
force=False,
is_root=False,
):
self.is_root = is_root
if self.is_root:
self.update_root(state=state, force=force)
else:
self.update_not_root(state=state, force=force)
def update_store(self, elements):
def do_update_store(self, elements):
new_store = []
selected_idx = None
for i, el in enumerate(elements):
# if state and state.selected_artist_id == el.id:
# selected_idx = i
for idx, el in enumerate(elements):
if el.id == self.selected_id:
selected_idx = idx
new_store.append(DrilldownList.DrilldownElement(el))
new_store.append(DirectoryList.SubelementModel(el.id, el.name))
util.diff_model_store(self.directory_list_store, new_store)
util.diff_model_store(self.drilldown_list_store, new_store)
# Preserve selection
if selected_idx is not None:
@@ -148,28 +180,95 @@ class DirectoryList(Gtk.Box):
self.loading_indicator.hide()
class IndexList(DrilldownList):
def update(
self,
selected_id,
state: ApplicationState = None,
force=False,
**kwargs,
):
self.selected_id = selected_id
self.update_store(force=force, state=state)
def on_refresh_clicked(self, _):
self.update(self.selected_id, force=True)
@util.async_callback(
lambda *a, **k: CacheManager.get_indexes(*a, **k),
before_download=lambda self: self.loading_indicator.show_all(),
on_failure=lambda self, e: self.loading_indicator.hide(),
)
def update_root(
def update_store(
self,
artists,
state: ApplicationState = None,
force=False,
):
self.update_store(artists)
self.do_update_store(artists)
def create_row(self, model: DrilldownList.DrilldownElement):
row = Gtk.ListBoxRow(
action_name='app.browse-to',
action_target=GLib.Variant('s', model.id),
)
row.add(
Gtk.Label(
label=f'<b>{util.esc(model.name)}</b>',
use_markup=True,
margin=10,
halign=Gtk.Align.START,
ellipsize=Pango.EllipsizeMode.END,
max_width_chars=30,
))
row.show_all()
return row
class MusicDirectoryList(DrilldownList):
def update(
self,
selected_id,
state: ApplicationState = None,
force=False,
directory_id=None,
):
self.directory_id = directory_id
self.selected_id = selected_id
self.update_store(directory_id, force=force, state=state)
def on_refresh_clicked(self, _):
self.update(
self.selected_id, force=True, directory_id=self.directory_id)
@util.async_callback(
lambda *a, **k: CacheManager.get_music_directory(*a, **k),
before_download=lambda self: self.loading_indicator.show_all(),
on_failure=lambda self, e: self.loading_indicator.hide(),
)
def update_not_root(
def update_store(
self,
directory,
state: ApplicationState = None,
force=False,
):
self.update_store(directory.child)
self.do_update_store(directory.child)
def create_row(self, model: DrilldownList.DrilldownElement):
row = Gtk.ListBoxRow()
if model.is_dir:
row.set_action_name('app.browse-to')
row.set_action_target_value(GLib.Variant('s', model.id))
row.add(
Gtk.Label(
label=f'<b>{util.esc(model.name)}</b>',
use_markup=True,
margin=10,
halign=Gtk.Align.START,
ellipsize=Pango.EllipsizeMode.END,
max_width_chars=30,
))
row.show_all()
return row

View File

@@ -194,7 +194,7 @@ def show_song_popover(
browse_to_song = Gtk.ModelButton(
text=f"Browse to {pluralize('song', song_count)}",
action_name='app.browse-to-song',
action_name='app.browse-to',
)
if len(parents) == 1 and list(parents)[0] is not None:
parent_value = GLib.Variant('s', list(parents)[0])