279 lines
10 KiB
Python
279 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import gi
|
|
import math
|
|
import threading
|
|
|
|
gi.require_version('Gtk', '4.0')
|
|
gi.require_version('Adw', '1')
|
|
from gi.repository import Gtk, Gdk, Gio, GObject, GdkPixbuf, Adw
|
|
from mcg import client
|
|
from mcg.albumheaderbar import AlbumHeaderbar
|
|
from mcg.utils import Utils
|
|
from mcg.utils import GridItem
|
|
|
|
|
|
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/playlist-panel.ui')
|
|
class PlaylistPanel(Adw.Bin):
|
|
__gtype_name__ = 'McgPlaylistPanel'
|
|
__gsignals__ = {
|
|
'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
'clear-playlist': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
'remove-album':
|
|
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
|
|
'remove-multiple-albums':
|
|
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
|
|
'play': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
|
|
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
|
|
}
|
|
|
|
# Widgets
|
|
playlist_stack = Gtk.Template.Child()
|
|
panel_normal = Gtk.Template.Child()
|
|
panel_standalone = Gtk.Template.Child()
|
|
actionbar_revealer = Gtk.Template.Child()
|
|
# Toolbar
|
|
toolbar = Gtk.Template.Child()
|
|
playlist_clear_button = Gtk.Template.Child()
|
|
select_button = Gtk.Template.Child()
|
|
# Playlist Grid
|
|
playlist_grid = Gtk.Template.Child()
|
|
# Action bar (normal)
|
|
actionbar = Gtk.Template.Child()
|
|
actionbar_standalone = Gtk.Template.Child()
|
|
# Standalone Image
|
|
standalone_stack = Gtk.Template.Child()
|
|
standalone_spinner = Gtk.Template.Child()
|
|
standalone_scroll = Gtk.Template.Child()
|
|
standalone_image = Gtk.Template.Child()
|
|
|
|
def __init__(self, client, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self._client = client
|
|
self._host = None
|
|
self._item_size = 150
|
|
self._playlist = None
|
|
self._playlist_albums = None
|
|
self._playlist_lock = threading.Lock()
|
|
self._playlist_stop = threading.Event()
|
|
self._icon_theme = Gtk.IconTheme.get_for_display(
|
|
Gdk.Display.get_default())
|
|
self._standalone_pixbuf = None
|
|
self._selected_albums = []
|
|
self._is_selected = False
|
|
|
|
# Widgets
|
|
# Header bar
|
|
self._headerbar_standalone = AlbumHeaderbar()
|
|
self._headerbar_standalone.connect('close',
|
|
self.on_headerbar_close_clicked)
|
|
# Playlist Grid: Model
|
|
self._playlist_grid_model = Gio.ListStore()
|
|
self._playlist_grid_selection_multi = Gtk.MultiSelection.new(
|
|
self._playlist_grid_model)
|
|
self._playlist_grid_selection_single = Gtk.SingleSelection.new(
|
|
self._playlist_grid_model)
|
|
# Playlist Grid
|
|
self.playlist_grid.set_model(self._playlist_grid_selection_single)
|
|
|
|
def get_headerbar_standalone(self):
|
|
return self._headerbar_standalone
|
|
|
|
def get_toolbar(self):
|
|
return self.toolbar
|
|
|
|
def set_selected(self, selected):
|
|
self._is_selected = selected
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_select_toggled(self, widget):
|
|
if self.select_button.get_active():
|
|
self.actionbar_revealer.set_reveal_child(True)
|
|
self.playlist_grid.set_model(self._playlist_grid_selection_multi)
|
|
self.playlist_grid.set_single_click_activate(False)
|
|
self.playlist_grid.get_style_context().add_class(
|
|
Utils.CSS_SELECTION)
|
|
else:
|
|
self.actionbar_revealer.set_reveal_child(False)
|
|
self.playlist_grid.set_model(self._playlist_grid_selection_single)
|
|
self.playlist_grid.set_single_click_activate(True)
|
|
self.playlist_grid.get_style_context().remove_class(
|
|
Utils.CSS_SELECTION)
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_clear_clicked(self, widget):
|
|
self.emit('clear-playlist')
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_playlist_grid_clicked(self, widget, position):
|
|
# Get selected album
|
|
item = self._playlist_grid_model.get_item(position)
|
|
album = item.get_album()
|
|
album_id = album.get_id()
|
|
self._selected_albums = [album]
|
|
self.emit('albumart', album_id)
|
|
|
|
# Show standalone album
|
|
if widget.get_model() == self._playlist_grid_selection_single:
|
|
# Set labels
|
|
self._headerbar_standalone.set_album(album)
|
|
|
|
# Show panel
|
|
self._open_standalone()
|
|
|
|
# Set cover loading indicator
|
|
self.standalone_stack.set_visible_child(self.standalone_spinner)
|
|
self.standalone_spinner.start()
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_selection_cancel_clicked(self, widget):
|
|
self.select_button.set_active(False)
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_selection_remove_clicked(self, widget):
|
|
self.emit('remove-multiple-albums', self._get_selected_albums())
|
|
self.select_button.set_active(False)
|
|
|
|
def on_headerbar_close_clicked(self, widget):
|
|
self._close_standalone()
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_standalone_remove_clicked(self, widget):
|
|
self.emit('remove-album', self._get_selected_albums()[0])
|
|
self._close_standalone()
|
|
|
|
@Gtk.Template.Callback()
|
|
def on_standalone_play_clicked(self, widget):
|
|
self.emit('play', self._get_selected_albums()[0])
|
|
self._close_standalone()
|
|
|
|
def set_size(self, width, height):
|
|
self._resize_standalone_image()
|
|
|
|
def set_item_size(self, item_size):
|
|
if self._item_size != item_size:
|
|
self._item_size = item_size
|
|
self._redraw()
|
|
|
|
def get_item_size(self):
|
|
return self._item_size
|
|
|
|
def set_playlist(self, host, playlist):
|
|
self._host = host
|
|
self._playlist_stop.set()
|
|
threading.Thread(target=self._set_playlist,
|
|
args=(
|
|
host,
|
|
playlist,
|
|
self._item_size,
|
|
)).start()
|
|
|
|
def set_albumart(self, album, data):
|
|
if album in self._selected_albums:
|
|
if data:
|
|
# Load image and draw it
|
|
try:
|
|
self._standalone_pixbuf = Utils.load_pixbuf(data)
|
|
except Exception:
|
|
self._logger.exception("Failed to set albumart")
|
|
self._cover_pixbuf = self._get_default_image()
|
|
else:
|
|
self._cover_pixbuf = self._get_default_image()
|
|
# Show image
|
|
GObject.idle_add(self._show_image)
|
|
|
|
def stop_threads(self):
|
|
self._playlist_stop.set()
|
|
|
|
def _set_playlist(self, host, playlist, size):
|
|
self._playlist_lock.acquire()
|
|
self._playlist_stop.clear()
|
|
self._playlist = playlist
|
|
self._playlist_albums = {}
|
|
for album in playlist:
|
|
self._playlist_albums[album.get_id()] = album
|
|
self._playlist_grid_model.remove_all()
|
|
|
|
cache = client.MCGCache(host, size)
|
|
for album in playlist:
|
|
pixbuf = None
|
|
# Load albumart thumbnail
|
|
try:
|
|
pixbuf = Utils.load_thumbnail(cache, self._client, album, size)
|
|
except client.CommandException:
|
|
# Exception is handled by client
|
|
pass
|
|
except Exception:
|
|
self._logger.exception("Failed to load albumart")
|
|
if pixbuf is None:
|
|
pixbuf = self._icon_theme.lookup_icon(
|
|
Utils.STOCK_ICON_DEFAULT, None, self._item_size,
|
|
self._item_size, Gtk.TextDirection.LTR,
|
|
Gtk.IconLookupFlags.FORCE_SYMBOLIC)
|
|
if pixbuf is not None:
|
|
self._playlist_grid_model.append(GridItem(album, pixbuf))
|
|
|
|
if self._playlist_stop.is_set():
|
|
self._playlist_lock.release()
|
|
return
|
|
|
|
self.playlist_grid.set_model(self._playlist_grid_selection_single)
|
|
self._playlist_lock.release()
|
|
|
|
def _show_image(self):
|
|
self._resize_standalone_image()
|
|
self.standalone_stack.set_visible_child(self.standalone_scroll)
|
|
self.standalone_spinner.stop()
|
|
|
|
def _redraw(self):
|
|
if self._playlist is not None:
|
|
self.set_playlist(self._host, self._playlist)
|
|
|
|
def _open_standalone(self):
|
|
self.playlist_stack.set_visible_child(self.panel_standalone)
|
|
self.emit('open-standalone')
|
|
|
|
def _close_standalone(self):
|
|
self.playlist_stack.set_visible_child(self.panel_normal)
|
|
self.emit('close-standalone')
|
|
|
|
def _resize_standalone_image(self):
|
|
# Get size
|
|
size_width = self.standalone_stack.get_width()
|
|
size_height = self.standalone_stack.get_height()
|
|
|
|
# Get pixelbuffer
|
|
pixbuf = self._standalone_pixbuf
|
|
# Check pixelbuffer
|
|
if pixbuf is None:
|
|
return
|
|
|
|
# Skalierungswert für Breite und Höhe ermitteln
|
|
ratio_w = float(size_width) / float(pixbuf.get_width())
|
|
ratio_h = float(size_height) / float(pixbuf.get_height())
|
|
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
|
|
ratio = min(ratio_w, ratio_h)
|
|
ratio = min(ratio, 1)
|
|
# Neue Breite und Höhe berechnen
|
|
width = int(math.floor(pixbuf.get_width() * ratio))
|
|
height = int(math.floor(pixbuf.get_height() * ratio))
|
|
if width <= 0 or height <= 0:
|
|
return
|
|
# Pixelpuffer auf Oberfläche zeichnen
|
|
self.standalone_image.set_from_pixbuf(
|
|
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
|
|
self.standalone_image.show()
|
|
|
|
def _get_default_image(self):
|
|
return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None,
|
|
512, 512, Gtk.TextDirection.LTR,
|
|
Gtk.IconLookupFlags.FORCE_SYMBOLIC)
|
|
|
|
def _get_selected_albums(self):
|
|
albums = []
|
|
for i in range(self.playlist_grid.get_model().get_n_items()):
|
|
if self.playlist_grid.get_model().is_selected(i):
|
|
albums.append(
|
|
self.playlist_grid.get_model().get_item(i).get_album())
|
|
return albums
|