#!/usr/bin/env python3 import gi gi.require_version('Gtk', '3.0') import logging import math from gi.repository import Gtk, Gdk, GObject, GdkPixbuf from mcg.utils import Utils @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/cover-toolbar.ui') class CoverToolbar(Gtk.ButtonBox): __gtype_name__ = 'McgCoverToolbar' __gsignals__ = { 'fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()) } # Widgets fullscreen_button = Gtk.Template.Child() def __init__(self): super().__init__() def set_fullscreen_sensitive(self, sensitive): self.fullscreen_button.set_sensitive(sensitive) @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 # Cover cover_stack = Gtk.Template.Child() cover_spinner = 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): super().__init__() self._current_album = None self._current_cover_album = None self._cover_pixbuf = None self._timer = None self._properties = {} self._icon_theme = Gtk.IconTheme.get_default() self._fullscreened = False self._is_selected = False self._current_size = None self._cover_pixbuf = self._get_default_image() # Widgets self._toolbar = CoverToolbar() self.cover_stack.set_visible_child(self.cover_scroll) # Initial actions GObject.idle_add(self._enable_tracklist) def get_toolbar(self): return self._toolbar def set_selected(self, selected): self._is_selected = selected @Gtk.Template.Callback() def on_cover_box_pressed(self, widget, event): if self._current_album and event.type == Gdk.EventType._2BUTTON_PRESS: self.emit('toggle-fullscreen') @Gtk.Template.Callback() def on_cover_size_allocate(self, widget, allocation): GObject.idle_add(self._resize_image) self.cover_info_scroll.set_max_content_width(allocation.width // 2) @Gtk.Template.Callback() def on_songs_start_change(self, widget, event): if self._timer: GObject.source_remove(self._timer) self._timer = None @Gtk.Template.Callback() def on_songs_change(self, widget, event): 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._toolbar.set_fullscreen_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) self.cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1)) GObject.idle_add(self._resize_image) self._fullscreened = True else: self._fullscreened = False self.info_revealer.set_reveal_child(True) self.cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 0)) 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 as e: self._logger.exception("Failed to set albumart") self._cover_pixbuf = self._get_default_image() else: # Reset image self._cover_pixbuf = self._get_default_image() 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 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): self._resize_image() self.cover_stack.set_visible_child(self.cover_scroll) self.cover_spinner.stop() def _resize_image(self): """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse """ # Get size size = self.cover_scroll.get_allocation() # Abort if size is the same if self._current_size and size.width == self._current_size.width and size.height == self._current_size.height: return self._current_size = size # Get pixelbuffer pixbuf = self._cover_pixbuf # Check pixelbuffer if pixbuf is None: return # Skalierungswert für Breite und Höhe ermitteln ratioW = float(size.width) / float(pixbuf.get_width()) ratioH = float(size.height) / float(pixbuf.get_height()) # Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren ratio = min(ratioW, ratioH) 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.cover_image.set_allocation(self.cover_scroll.get_allocation()) self.cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) self.cover_image.show() def _get_default_image(self): return self._icon_theme.load_icon( Utils.STOCK_ICON_DEFAULT, 512, Gtk.IconLookupFlags.FORCE_SVG & Gtk.IconLookupFlags.FORCE_SIZE )