239 lines
8.2 KiB
Python
239 lines
8.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import gi
|
|
import math
|
|
|
|
gi.require_version('Gtk', '4.0')
|
|
from gi.repository import Gtk, Gdk, GObject, GdkPixbuf
|
|
from mcg.utils import Utils
|
|
|
|
|
|
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/cover-panel.ui')
|
|
class CoverPanel(Gtk.Overlay):
|
|
__gtype_name__ = 'McgCoverPanel'
|
|
__gsignals__ = {
|
|
'toggle-fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
'set-song': (GObject.SIGNAL_RUN_FIRST, None, (int, int, )),
|
|
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, ))
|
|
}
|
|
|
|
# Widgets
|
|
# Toolbar
|
|
toolbar = Gtk.Template.Child()
|
|
fullscreen_button = Gtk.Template.Child()
|
|
# Cover
|
|
cover_stack = Gtk.Template.Child()
|
|
cover_spinner = Gtk.Template.Child()
|
|
cover_default = Gtk.Template.Child()
|
|
cover_scroll = Gtk.Template.Child()
|
|
cover_box = Gtk.Template.Child()
|
|
cover_image = Gtk.Template.Child()
|
|
# Album Infos
|
|
cover_info_scroll = Gtk.Template.Child()
|
|
info_revealer = Gtk.Template.Child()
|
|
album_title_label = Gtk.Template.Child()
|
|
album_date_label = Gtk.Template.Child()
|
|
album_artist_label = Gtk.Template.Child()
|
|
# Songs
|
|
songs_scale = Gtk.Template.Child()
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
self._current_album = None
|
|
self._current_cover_album = None
|
|
self._cover_pixbuf = None
|
|
self._timer = None
|
|
self._properties = {}
|
|
self._icon_theme = Gtk.IconTheme.get_for_display(
|
|
Gdk.Display.get_default())
|
|
self._fullscreened = False
|
|
self._current_size = None
|
|
|
|
# Initial actions
|
|
GObject.idle_add(self._enable_tracklist)
|
|
|
|
# Click handler for image
|
|
click_controller = Gtk.GestureClick()
|
|
click_controller.connect('pressed', self.on_cover_box_pressed)
|
|
self.cover_box.add_controller(click_controller)
|
|
|
|
# Button controller for songs scale
|
|
button_controller = Gtk.GestureClick()
|
|
button_controller.connect('pressed', self.on_songs_scale_pressed)
|
|
button_controller.connect('unpaired-release',
|
|
self.on_songs_scale_released)
|
|
self.songs_scale.add_controller(button_controller)
|
|
|
|
def get_toolbar(self):
|
|
return self.toolbar
|
|
|
|
def set_selected(self, selected):
|
|
"""The cover panel does not use selections"""
|
|
pass
|
|
|
|
def on_cover_box_pressed(self, widget, npress, x, y):
|
|
if self._current_album and npress == 2:
|
|
self.emit('toggle-fullscreen')
|
|
|
|
def set_width(self, width):
|
|
GObject.idle_add(self._resize_image)
|
|
self.cover_info_scroll.set_max_content_width(width // 2)
|
|
|
|
def on_songs_scale_pressed(self, widget, npress, x, y):
|
|
if self._timer:
|
|
GObject.source_remove(self._timer)
|
|
self._timer = None
|
|
|
|
def on_songs_scale_released(self, widget, x, y, npress, sequence):
|
|
value = int(self.songs_scale.get_value())
|
|
time = self._current_album.get_length()
|
|
tracks = self._current_album.get_tracks()
|
|
pos = 0
|
|
for index in range(len(tracks) - 1, -1, -1):
|
|
time = time - tracks[index].get_length()
|
|
pos = tracks[index].get_pos()
|
|
if time < value:
|
|
break
|
|
time = max(value - time - 1, 0)
|
|
self.emit('set-song', pos, time)
|
|
|
|
def set_album(self, album):
|
|
if album:
|
|
# Set labels
|
|
self.album_title_label.set_label(album.get_title())
|
|
self.album_date_label.set_label(', '.join(album.get_dates()))
|
|
self.album_artist_label.set_label(', '.join(
|
|
album.get_albumartists()))
|
|
|
|
# Set tracks
|
|
self._set_tracks(album)
|
|
|
|
# Load cover
|
|
if album != self._current_cover_album:
|
|
self.cover_stack.set_visible_child(self.cover_spinner)
|
|
self.cover_spinner.start()
|
|
self.emit('albumart', album.get_id() if album else None)
|
|
|
|
# Set current album
|
|
self._current_album = album
|
|
self._enable_tracklist()
|
|
self.fullscreen_button.set_sensitive(self._current_album is not None)
|
|
|
|
def set_play(self, pos, time):
|
|
if self._timer is not None:
|
|
GObject.source_remove(self._timer)
|
|
self._timer = None
|
|
tracks = self._current_album.get_tracks()
|
|
for index in range(0, pos):
|
|
time = time + tracks[index].get_length()
|
|
|
|
self.songs_scale.set_value(time + 1)
|
|
self._timer = GObject.timeout_add(1000, self._playing)
|
|
|
|
def set_pause(self):
|
|
if self._timer is not None:
|
|
GObject.source_remove(self._timer)
|
|
self._timer = None
|
|
|
|
def set_fullscreen(self, active):
|
|
if active:
|
|
self.info_revealer.set_reveal_child(False)
|
|
GObject.idle_add(self._resize_image)
|
|
self._fullscreened = True
|
|
else:
|
|
self._fullscreened = False
|
|
if self._current_album:
|
|
self.info_revealer.set_reveal_child(True)
|
|
GObject.idle_add(self._resize_image)
|
|
|
|
def set_albumart(self, album, data):
|
|
if album == self._current_album:
|
|
if data:
|
|
# Load image and draw it
|
|
try:
|
|
self._cover_pixbuf = Utils.load_pixbuf(data)
|
|
except Exception:
|
|
self._logger.exception("Failed to set albumart")
|
|
self._cover_pixbuf = None
|
|
else:
|
|
# Reset image
|
|
self._cover_pixbuf = None
|
|
self._current_size = None
|
|
self._current_cover_album = album
|
|
# Show image
|
|
GObject.idle_add(self._show_image)
|
|
|
|
def _set_tracks(self, album):
|
|
self.songs_scale.clear_marks()
|
|
self.songs_scale.set_range(0, album.get_length())
|
|
length = 0
|
|
for track in album.get_tracks():
|
|
cur_length = length
|
|
if length > 0 and length < album.get_length():
|
|
cur_length = cur_length + 1
|
|
self.songs_scale.add_mark(
|
|
cur_length, Gtk.PositionType.RIGHT,
|
|
GObject.markup_escape_text(Utils.create_track_title(track)))
|
|
length = length + track.get_length()
|
|
self.songs_scale.add_mark(
|
|
length, Gtk.PositionType.RIGHT,
|
|
"{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60)))
|
|
|
|
def _enable_tracklist(self):
|
|
if self._current_album:
|
|
# enable
|
|
if not self._fullscreened:
|
|
self.info_revealer.set_reveal_child(True)
|
|
else:
|
|
# disable
|
|
self.info_revealer.set_reveal_child(False)
|
|
|
|
def _playing(self):
|
|
value = self.songs_scale.get_value() + 1
|
|
self.songs_scale.set_value(value)
|
|
|
|
return True
|
|
|
|
def _show_image(self):
|
|
if self._cover_pixbuf:
|
|
self._resize_image()
|
|
self.cover_stack.set_visible_child(self.cover_scroll)
|
|
else:
|
|
self.cover_stack.set_visible_child(self.cover_default)
|
|
self.cover_spinner.stop()
|
|
|
|
def _resize_image(self):
|
|
# Get size
|
|
size_width = self.cover_stack.get_size(Gtk.Orientation.HORIZONTAL)
|
|
size_height = self.cover_stack.get_size(Gtk.Orientation.HORIZONTAL)
|
|
# Abort if size is the same
|
|
if self._current_size:
|
|
current_width, current_height = self._current_size
|
|
if size_width == current_width and size_height == current_height:
|
|
return
|
|
self._current_size = (
|
|
size_width,
|
|
size_height,
|
|
)
|
|
|
|
# Get pixelbuffer
|
|
pixbuf = self._cover_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
|
|
self.cover_image.set_from_pixbuf(
|
|
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
|
|
self.cover_image.show()
|