Made Browse tab fairly functional
This commit is contained in:
@@ -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):
|
||||
|
@@ -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())
|
||||
|
@@ -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(
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
#playlist-list-spinner:checked,
|
||||
#artist-list-spinner:checked,
|
||||
#directory-list-spinner:checked {
|
||||
#drilldown-list-spinner:checked {
|
||||
margin: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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])
|
||||
|
Reference in New Issue
Block a user