Use GTK Composite Templates (close #62)

Use GTK Composite Templates for GUI elements to clean up and simplify
the code for widgets and all UI elements. This includes splitting the
large “gtk.glade” file into smaller .ui files and the large “widgets.py”
file into smaller .py files.
This commit is contained in:
coderkun 2020-08-02 17:18:28 +02:00
commit ba373ddf4e
31 changed files with 4431 additions and 4300 deletions

279
mcg/coverpanel.py Normal file
View file

@ -0,0 +1,279 @@
#!/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='/de/coderkun/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='/de/coderkun/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
# Show image
self._resize_image()
self.cover_stack.set_visible_child(self.cover_scroll)
self.cover_spinner.stop()
self._current_cover_album = album
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 _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
)