Menu for the playlist song list

This commit is contained in:
Sumner Evans
2019-07-05 23:37:14 -06:00
parent 44ec7b0369
commit e34c64219d
6 changed files with 180 additions and 28 deletions

View File

@@ -160,7 +160,6 @@ class CacheManager(metaclass=Singleton):
self.current_downloads.add(abs_path)
os.makedirs(download_path.parent, exist_ok=True)
download_path.touch()
before_download()
self.save_file(download_path, download_fn())

View File

@@ -1,6 +1,7 @@
/* ********** Main ********** */
#connected-to-label {
margin: 0 10px;
margin-top: 10px;
margin-bottom: 10px;
}
/* ********** Playlist ********** */
@@ -95,3 +96,7 @@
#up-next-popover #label {
margin: 10px;
}
.menu-button {
padding: 5px;
}

View File

@@ -109,14 +109,15 @@ class MainWindow(Gtk.ApplicationWindow):
menu_items = [
(None, self.connected_to_label),
('app.configure-servers',
Gtk.ModelButton(label='Connect to Server')),
Gtk.ModelButton(text='Connect to Server')),
]
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
for name, item in menu_items:
if name:
item.set_action_name(name)
vbox.pack_start(item, False, True, 10)
item.get_style_context().add_class('menu-button')
vbox.pack_start(item, False, True, 0)
self.menu.add(vbox)
return self.menu

View File

@@ -174,8 +174,8 @@ class PlayerControls(Gtk.ActionBar):
def on_up_next_click(self, button):
self.up_next_popover.set_relative_to(button)
self.up_next_popover.show_all()
self.up_next_popover.popup()
self.up_next_popover.show_all()
def create_song_display(self):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)

View File

@@ -25,7 +25,7 @@ class EditPlaylistDialog(util.EditFormDialog):
boolean_fields = [('Public', 'public')]
def __init__(self, *args, **kwargs):
delete_playlist = Gtk.Button('Delete Playlist')
delete_playlist = Gtk.Button(label='Delete Playlist')
delete_playlist.connect('clicked', self.on_delete_playlist_click)
self.extra_buttons = [delete_playlist]
super().__init__(*args, **kwargs)
@@ -156,24 +156,28 @@ class PlaylistsPanel(Gtk.Paned):
# Action buttons (note we are packing end here, so we have to put them
# in right-to-left).
# TODO hide this if there is no selected playlist
action_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.playlist_action_buttons = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL)
view_refresh_button = util.button_with_icon('view-refresh-symbolic')
view_refresh_button.connect('clicked', self.on_view_refresh_click)
action_button_box.pack_end(view_refresh_button, False, False, 5)
self.playlist_action_buttons.pack_end(view_refresh_button, False,
False, 5)
playlist_edit_button = util.button_with_icon('document-edit-symbolic')
playlist_edit_button.connect('clicked',
self.on_playlist_edit_button_click)
action_button_box.pack_end(playlist_edit_button, False, False, 5)
self.playlist_action_buttons.pack_end(playlist_edit_button, False,
False, 5)
download_all_button = util.button_with_icon('folder-download-symbolic')
download_all_button.connect(
'clicked', self.on_playlist_list_download_all_button_click)
action_button_box.pack_end(download_all_button, False, False, 5)
self.playlist_action_buttons.pack_end(download_all_button, False,
False, 5)
playlist_details_box.pack_start(action_button_box, False, False, 5)
playlist_details_box.pack_start(self.playlist_action_buttons, False,
False, 5)
playlist_details_box.pack_start(Gtk.Box(), True, False, 0)
@@ -224,10 +228,12 @@ class PlaylistsPanel(Gtk.Paned):
self.playlist_songs.get_selection().set_mode(
Gtk.SelectionMode.MULTIPLE)
# TODO: add playing/menu column which shows whether a song is playing,
# and when hovered shows a 3-dot menu (the same as right click menu).
# Song status column.
renderer = Gtk.CellRendererPixbuf()
renderer.set_fixed_size(30, 35)
column = Gtk.TreeViewColumn('', renderer, icon_name=0)
column.set_resizable(True)
self.playlist_songs.append_column(column)
@@ -276,11 +282,7 @@ class PlaylistsPanel(Gtk.Paned):
self.new_playlist_row.show()
def on_playlist_selected(self, playlist_list, row):
row_idx = row.get_index()
# TODO don't update if selecting the same playlist.
# Use row index - 1 due to the loading indicator.
self.update_playlist_view(self.playlist_map[row_idx].id)
self.update_playlist_view(self.playlist_map[row.get_index()].id)
def on_list_refresh_click(self, button):
self.update_playlist_list(force=True)
@@ -316,8 +318,6 @@ class PlaylistsPanel(Gtk.Paned):
dialog.destroy()
def on_playlist_list_download_all_button_click(self, button):
# TODO make this rate-limited so that it doesn't overwhelm the thread
# pool.
playlist = self.playlist_map[
self.playlist_list.get_selected_row().get_index()]
@@ -342,12 +342,54 @@ class PlaylistsPanel(Gtk.Paned):
self.emit('song-clicked', song_id,
[m[-1] for m in self.playlist_song_model])
def on_song_button_press(self, tree, button):
if button.button == 3: # Right click
print('right click')
# TODO show the popup
# TODO should probably have a menu button on each of the songs in
# the list.
def on_song_button_press(self, tree, event):
if event.button == 3: # Right click
clicked_path = tree.get_path_at_pos(event.x, event.y)[0]
store, paths = tree.get_selection().get_selected_rows()
allow_deselect = False
playlist = self.playlist_map[
self.playlist_list.get_selected_row().get_index()]
def on_download_state_change(song_id=None):
GLib.idle_add(self.update_playlist_song_list, playlist.id)
# Use the new selection instead of the old one for calculating what
# to do the right click on.
if clicked_path not in paths:
paths = [clicked_path]
allow_deselect = True
song_ids = [self.playlist_song_model[p][-1] for p in paths]
# Used to adjust for the header row.
bin_coords = tree.convert_tree_to_bin_window_coords(
event.x, event.y)
widget_coords = tree.convert_tree_to_widget_coords(
event.x, event.y)
def on_remove_songs_click(button):
CacheManager.update_playlist(
playlist_id=playlist.id,
song_index_to_remove=[p.get_indices()[0] for p in paths],
)
self.update_playlist_song_list(playlist.id, force=True)
remove_text = f"Remove {util.pluralize('song', len(song_ids))} from playlist"
util.show_song_popover(
song_ids,
event.x,
event.y + abs(bin_coords.by - widget_coords.wy),
tree,
on_download_state_change=on_download_state_change,
extra_menu_items=[(
Gtk.ModelButton(text=remove_text),
on_remove_songs_click,
)])
# If the click was on a selected row, don't deselect anything.
if not allow_deselect:
return True
def on_playlist_list_new_entry_activate(self, entry):
self.create_playlist(entry.get_text())
@@ -392,6 +434,9 @@ class PlaylistsPanel(Gtk.Paned):
if selected:
playlist_id = self.playlist_map[selected.get_index()].id
self.update_playlist_view(playlist_id)
self.playlist_action_buttons.show()
else:
self.playlist_action_buttons.hide()
def set_playlist_list_loading(self, loading_status):
if loading_status:
@@ -472,6 +517,7 @@ class PlaylistsPanel(Gtk.Paned):
self.playlist_stats.set_markup(self.format_stats(playlist))
self.update_playlist_song_list(playlist.id)
self.playlist_action_buttons.show()
@util.async_callback(
lambda *a, **k: CacheManager.get_playlist(*a, **k),
@@ -533,6 +579,7 @@ class PlaylistsPanel(Gtk.Paned):
@util.async_callback(
lambda *a, **k: CacheManager.get_playlist(*a, **k),
# TODO make loading here
)
def update_playlist_order(self, playlist):
CacheManager.update_playlist(

View File

@@ -1,11 +1,13 @@
import functools
from typing import List, Tuple
from typing import Callable, List, Tuple, Any, Optional
from concurrent.futures import Future
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk, GObject, GLib
from gi.repository import Gio, Gtk, GObject, GLib, Gdk
from libremsonic.cache_manager import CacheManager, SongCacheStatus
def button_with_icon(
@@ -38,6 +40,104 @@ def esc(string):
return string.replace('&', '&')
def show_song_popover(
song_ids,
x: int,
y: int,
relative_to: Any,
position: Gtk.PositionType = Gtk.PositionType.BOTTOM,
on_download_state_change: Callable[[int], None] = lambda x: None,
show_remove_from_playlist_button: bool = False,
extra_menu_items: List[Tuple[Gtk.ModelButton, Any]] = [],
):
def on_download_songs_click(button):
CacheManager.batch_download_songs(
song_ids,
before_download=on_download_state_change,
on_song_download_complete=on_download_state_change,
)
def on_add_to_playlist_click(button, playlist):
CacheManager.executor.submit(
CacheManager.update_playlist,
playlist_id=playlist.id,
song_id_to_add=song_ids,
)
popover = Gtk.PopoverMenu()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Add all of the menu items to the popover.
song_count = len(song_ids)
# Determine if we should enable the download button.
sensitive = False
for song_id in song_ids:
details = CacheManager.get_song_details(song_id)
status = CacheManager.get_cached_status(details.result())
if status == SongCacheStatus.NOT_CACHED:
sensitive = True
break
menu_items = [
(
Gtk.ModelButton(
text=(f"Download {pluralize('song', song_count)}"
if song_count > 1 else 'Download Song'),
sensitive=sensitive,
),
on_download_songs_click,
),
(
Gtk.ModelButton(
text=f"Add {pluralize('song', song_count)} to playlist",
menu_name='add-to-playlist',
),
None,
),
*extra_menu_items,
]
for button, action in menu_items:
if action:
button.connect('clicked', action)
button.get_style_context().add_class('menu-button')
vbox.pack_start(button, False, True, 0)
popover.add(vbox)
# Create the "Add song(s) to playlist" sub-menu.
playlists_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Back button
playlists_vbox.add(
Gtk.ModelButton(
inverted=True,
centered=True,
menu_name='main',
))
# The playlist buttons
for playlist in CacheManager.get_playlists().result():
button = Gtk.ModelButton(text=playlist.name)
button.get_style_context().add_class('menu-button')
button.connect('clicked', on_add_to_playlist_click, playlist)
playlists_vbox.pack_start(button, False, True, 0)
popover.add(playlists_vbox)
popover.child_set_property(playlists_vbox, 'submenu', 'add-to-playlist')
# Positioning of the popover.
rect = Gdk.Rectangle()
rect.x, rect.y, rect.width, rect.height = x, y, 1, 1
popover.set_pointing_to(rect)
popover.set_position(position)
popover.set_relative_to(relative_to)
popover.popup()
popover.show_all()
class EditFormDialog(Gtk.Dialog):
entity_name: str
initial_size: Tuple[int, int]