Compare commits

...
14 changed files with 458 additions and 693 deletions

View file

@ -1,12 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
# Set environment # Set environment
srcdir = os.path.abspath(os.path.dirname(__file__)) srcdir = os.path.abspath(os.path.dirname(__file__))
datadir = os.path.join(srcdir, 'data') datadir = os.path.join(srcdir, 'data')
@ -23,19 +19,14 @@ if not os.environ.get('GSETTINGS_SCHEMA_DIR'):
os.environ['GSETTINGS_SCHEMA_DIR'] = datadirdev os.environ['GSETTINGS_SCHEMA_DIR'] = datadirdev
class Environment: class Environment:
"""Wrapper class to access environment settings.""" """Wrapper class to access environment settings."""
def get_srcdir(): def get_srcdir():
return srcdir return srcdir
def get_data(subdir): def get_data(subdir):
return os.path.join(datadir, subdir) return os.path.join(datadir, subdir)
def get_locale(): def get_locale():
return localedir return localedir

View file

@ -1,36 +1,28 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
from gi.repository import Gtk, GObject, Adw from gi.repository import Gtk, GObject, Adw
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/album-headerbar.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/album-headerbar.ui')
class AlbumHeaderbar(Adw.Bin): class AlbumHeaderbar(Adw.Bin):
__gtype_name__ = 'McgAlbumHeaderbar' __gtype_name__ = 'McgAlbumHeaderbar'
__gsignals__ = { __gsignals__ = {'close': (GObject.SIGNAL_RUN_FIRST, None, ())}
'close': (GObject.SIGNAL_RUN_FIRST, None, ())
}
# Widgets # Widgets
standalone_title = Gtk.Template.Child() standalone_title = Gtk.Template.Child()
standalone_artist = Gtk.Template.Child() standalone_artist = Gtk.Template.Child()
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_close_clicked(self, widget): def on_close_clicked(self, widget):
self.emit('close') self.emit('close')
def set_album(self, album): def set_album(self, album):
self.standalone_title.set_text(album.get_title()) self.standalone_title.set_text(album.get_title())
self.standalone_artist.set_text(", ".join(album.get_albumartists())) self.standalone_artist.set_text(", ".join(album.get_albumartists()))

View file

@ -1,27 +1,22 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import logging
import urllib
import gi import gi
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
from gi.repository import Gio, Gtk, Gdk, GLib, Adw from gi.repository import Gio, Gtk, Gdk, Adw
from .window import Window from .window import Window
class Application(Gtk.Application): class Application(Gtk.Application):
TITLE = "CoverGrid" TITLE = "CoverGrid"
ID = 'xyz.suruatoel.mcg' ID = 'xyz.suruatoel.mcg'
DOMAIN = 'mcg' DOMAIN = 'mcg'
def __init__(self): def __init__(self):
super().__init__(application_id=Application.ID, flags=Gio.ApplicationFlags.FLAGS_NONE) super().__init__(application_id=Application.ID,
flags=Gio.ApplicationFlags.FLAGS_NONE)
self._window = None self._window = None
self._info_dialog = None self._info_dialog = None
self._verbosity = logging.WARNING self._verbosity = logging.WARNING
@ -38,7 +33,6 @@ class Application(Gtk.Application):
self.set_accels_for_action('win.panel("2")', ['<primary>KP_3']) self.set_accels_for_action('win.panel("2")', ['<primary>KP_3'])
self.set_accels_for_action('win.panel("3")', ['<primary>KP_4']) self.set_accels_for_action('win.panel("3")', ['<primary>KP_4'])
def do_startup(self): def do_startup(self):
Gtk.Application.do_startup(self) Gtk.Application.do_startup(self)
self._setup_logging() self._setup_logging()
@ -48,55 +42,46 @@ class Application(Gtk.Application):
self._setup_actions() self._setup_actions()
self._setup_adw() self._setup_adw()
def do_activate(self): def do_activate(self):
Gtk.Application.do_activate(self) Gtk.Application.do_activate(self)
if not self._window: if not self._window:
self._window = Window(self, Application.TITLE, self._settings) self._window = Window(self, Application.TITLE, self._settings)
self._window.present() self._window.present()
def on_menu_info(self, action, value): def on_menu_info(self, action, value):
self._info_dialog = Adw.AboutDialog() self._info_dialog = Adw.AboutDialog()
self._info_dialog.set_application_icon("xyz.suruatoel.mcg") self._info_dialog.set_application_icon("xyz.suruatoel.mcg")
self._info_dialog.set_application_name("CoverGrid") self._info_dialog.set_application_name("CoverGrid")
self._info_dialog.set_version("3.2.1") self._info_dialog.set_version("3.2.1")
self._info_dialog.set_comments("CoverGrid is a client for the Music Player Daemon, focusing on albums instead of single tracks.") self._info_dialog.set_comments(
"""CoverGrid is a client for the Music Player Daemon, focusing on \
albums instead of single tracks.""")
self._info_dialog.set_website("https://www.suruatoel.xyz/codes/mcg") self._info_dialog.set_website("https://www.suruatoel.xyz/codes/mcg")
self._info_dialog.set_license_type(Gtk.License.GPL_3_0) self._info_dialog.set_license_type(Gtk.License.GPL_3_0)
self._info_dialog.set_issue_url("https://git.suruatoel.xyz/coderkun/mcg") self._info_dialog.set_issue_url(
"https://git.suruatoel.xyz/coderkun/mcg")
self._info_dialog.present() self._info_dialog.present()
def on_menu_quit(self, action, value): def on_menu_quit(self, action, value):
self.quit() self.quit()
def _setup_logging(self): def _setup_logging(self):
logging.basicConfig( logging.basicConfig(level=self._verbosity,
level=self._verbosity, format="%(asctime)s %(levelname)s: %(message)s")
format="%(asctime)s %(levelname)s: %(message)s"
)
def _load_settings(self): def _load_settings(self):
self._settings = Gio.Settings.new(Application.ID) self._settings = Gio.Settings.new(Application.ID)
def _set_default_settings(self): def _set_default_settings(self):
style_manager = Adw.StyleManager.get_default() style_manager = Adw.StyleManager.get_default()
style_manager.set_color_scheme(Adw.ColorScheme.PREFER_DARK) style_manager.set_color_scheme(Adw.ColorScheme.PREFER_DARK)
def _load_css(self): def _load_css(self):
styleProvider = Gtk.CssProvider() styleProvider = Gtk.CssProvider()
styleProvider.load_from_resource(self._get_resource_path('gtk.css')) styleProvider.load_from_resource(self._get_resource_path('gtk.css'))
Gtk.StyleContext.add_provider_for_display( Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(), Gdk.Display.get_default(), styleProvider,
styleProvider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
def _setup_actions(self): def _setup_actions(self):
action = Gio.SimpleAction.new("info", None) action = Gio.SimpleAction.new("info", None)
@ -106,11 +91,9 @@ class Application(Gtk.Application):
action.connect('activate', self.on_menu_quit) action.connect('activate', self.on_menu_quit)
self.add_action(action) self.add_action(action)
def _get_resource_path(self, path): def _get_resource_path(self, path):
return "/{}/{}".format(Application.ID.replace('.', '/'), path) return "/{}/{}".format(Application.ID.replace('.', '/'), path)
def _setup_adw(self): def _setup_adw(self):
Adw.HeaderBar() Adw.HeaderBar()
Adw.ToolbarView() Adw.ToolbarView()

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import locale import locale
from gi.repository import Gtk, Gio, GObject, Adw gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, GObject, Adw
from mcg.zeroconf import ZeroconfProvider from mcg.zeroconf import ZeroconfProvider
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/connection-panel.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/connection-panel.ui')
class ConnectionPanel(Adw.Bin): class ConnectionPanel(Adw.Bin):
__gtype_name__ = 'McgConnectionPanel' __gtype_name__ = 'McgConnectionPanel'
@ -27,14 +23,13 @@ class ConnectionPanel(Adw.Bin):
port_spinner = Gtk.Template.Child() port_spinner = Gtk.Template.Child()
password_row = Gtk.Template.Child() password_row = Gtk.Template.Child()
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# Zeroconf provider # Zeroconf provider
self._zeroconf_provider = ZeroconfProvider() self._zeroconf_provider = ZeroconfProvider()
self._zeroconf_provider.connect_signal(ZeroconfProvider.SIGNAL_SERVICE_NEW, self.on_new_service) self._zeroconf_provider.connect_signal(
ZeroconfProvider.SIGNAL_SERVICE_NEW, self.on_new_service)
def on_new_service(self, service): def on_new_service(self, service):
name, host, port = service name, host, port = service
@ -50,50 +45,45 @@ class ConnectionPanel(Adw.Bin):
self.zeroconf_list.insert(row, -1) self.zeroconf_list.insert(row, -1)
def on_service_selected(self, widget, host, port): def on_service_selected(self, widget, host, port):
self.set_host(host) self.set_host(host)
self.set_port(port) self.set_port(port)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_host_entry_apply(self, widget): def on_host_entry_apply(self, widget):
self._call_back() self._call_back()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_port_spinner_value_changed(self, widget): def on_port_spinner_value_changed(self, widget):
self._call_back() self._call_back()
def set_host(self, host): def set_host(self, host):
self.host_row.set_text(host) self.host_row.set_text(host)
def get_host(self): def get_host(self):
return self.host_row.get_text() return self.host_row.get_text()
def set_port(self, port): def set_port(self, port):
self.port_spinner.set_value(port) self.port_spinner.set_value(port)
def get_port(self): def get_port(self):
return self.port_spinner.get_value_as_int() return self.port_spinner.get_value_as_int()
def set_password(self, password): def set_password(self, password):
if password is None: if password is None:
password = "" password = ""
self.password_row.set_text(password) self.password_row.set_text(password)
def get_password(self): def get_password(self):
if self.password_row.get_text() == "": if self.password_row.get_text() == "":
return None return None
else: else:
return self.password_entry.get_text() return self.password_entry.get_text()
def _call_back(self): def _call_back(self):
self.emit('connection-changed', self.get_host(), self.get_port(), self.get_password(),) self.emit(
'connection-changed',
self.get_host(),
self.get_port(),
self.get_password(),
)

View file

@ -1,25 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0')
import logging
import math import math
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gdk, GObject, GdkPixbuf from gi.repository import Gtk, Gdk, GObject, GdkPixbuf
from mcg.utils import Utils from mcg.utils import Utils
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/cover-panel.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/cover-panel.ui')
class CoverPanel(Gtk.Overlay): class CoverPanel(Gtk.Overlay):
__gtype_name__ = 'McgCoverPanel' __gtype_name__ = 'McgCoverPanel'
__gsignals__ = { __gsignals__ = {
'toggle-fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()), 'toggle-fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()),
'set-song': (GObject.SIGNAL_RUN_FIRST, None, (int, int,)), 'set-song': (GObject.SIGNAL_RUN_FIRST, None, (int, int, )),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str,)) 'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, ))
} }
# Widgets # Widgets
@ -42,8 +37,6 @@ class CoverPanel(Gtk.Overlay):
# Songs # Songs
songs_scale = Gtk.Template.Child() songs_scale = Gtk.Template.Child()
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@ -52,7 +45,8 @@ class CoverPanel(Gtk.Overlay):
self._cover_pixbuf = None self._cover_pixbuf = None
self._timer = None self._timer = None
self._properties = {} self._properties = {}
self._icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()) self._icon_theme = Gtk.IconTheme.get_for_display(
Gdk.Display.get_default())
self._fullscreened = False self._fullscreened = False
self._current_size = None self._current_size = None
@ -67,28 +61,24 @@ class CoverPanel(Gtk.Overlay):
# Button controller for songs scale # Button controller for songs scale
buttonController = Gtk.GestureClick() buttonController = Gtk.GestureClick()
buttonController.connect('pressed', self.on_songs_scale_pressed) buttonController.connect('pressed', self.on_songs_scale_pressed)
buttonController.connect('unpaired-release', self.on_songs_scale_released) buttonController.connect('unpaired-release',
self.on_songs_scale_released)
self.songs_scale.add_controller(buttonController) self.songs_scale.add_controller(buttonController)
def get_toolbar(self): def get_toolbar(self):
return self.toolbar return self.toolbar
def set_selected(self, selected): def set_selected(self, selected):
pass pass
def on_cover_box_pressed(self, widget, npress, x, y): def on_cover_box_pressed(self, widget, npress, x, y):
if self._current_album and npress == 2: if self._current_album and npress == 2:
self.emit('toggle-fullscreen') self.emit('toggle-fullscreen')
def set_width(self, width): def set_width(self, width):
GObject.idle_add(self._resize_image) GObject.idle_add(self._resize_image)
self.cover_info_scroll.set_max_content_width(width // 2) self.cover_info_scroll.set_max_content_width(width // 2)
def on_songs_scale_pressed(self, widget, npress, x, y): def on_songs_scale_pressed(self, widget, npress, x, y):
if self._timer: if self._timer:
GObject.source_remove(self._timer) GObject.source_remove(self._timer)
@ -99,7 +89,7 @@ class CoverPanel(Gtk.Overlay):
time = self._current_album.get_length() time = self._current_album.get_length()
tracks = self._current_album.get_tracks() tracks = self._current_album.get_tracks()
pos = 0 pos = 0
for index in range(len(tracks)-1, -1, -1): for index in range(len(tracks) - 1, -1, -1):
time = time - tracks[index].get_length() time = time - tracks[index].get_length()
pos = tracks[index].get_pos() pos = tracks[index].get_pos()
if time < value: if time < value:
@ -107,13 +97,13 @@ class CoverPanel(Gtk.Overlay):
time = max(value - time - 1, 0) time = max(value - time - 1, 0)
self.emit('set-song', pos, time) self.emit('set-song', pos, time)
def set_album(self, album): def set_album(self, album):
if album: if album:
# Set labels # Set labels
self.album_title_label.set_label(album.get_title()) self.album_title_label.set_label(album.get_title())
self.album_date_label.set_label(', '.join(album.get_dates())) self.album_date_label.set_label(', '.join(album.get_dates()))
self.album_artist_label.set_label(', '.join(album.get_albumartists())) self.album_artist_label.set_label(', '.join(
album.get_albumartists()))
# Set tracks # Set tracks
self._set_tracks(album) self._set_tracks(album)
@ -129,7 +119,6 @@ class CoverPanel(Gtk.Overlay):
self._enable_tracklist() self._enable_tracklist()
self.fullscreen_button.set_sensitive(self._current_album is not None) self.fullscreen_button.set_sensitive(self._current_album is not None)
def set_play(self, pos, time): def set_play(self, pos, time):
if self._timer is not None: if self._timer is not None:
GObject.source_remove(self._timer) GObject.source_remove(self._timer)
@ -138,16 +127,14 @@ class CoverPanel(Gtk.Overlay):
for index in range(0, pos): for index in range(0, pos):
time = time + tracks[index].get_length() time = time + tracks[index].get_length()
self.songs_scale.set_value(time+1) self.songs_scale.set_value(time + 1)
self._timer = GObject.timeout_add(1000, self._playing) self._timer = GObject.timeout_add(1000, self._playing)
def set_pause(self): def set_pause(self):
if self._timer is not None: if self._timer is not None:
GObject.source_remove(self._timer) GObject.source_remove(self._timer)
self._timer = None self._timer = None
def set_fullscreen(self, active): def set_fullscreen(self, active):
if active: if active:
self.info_revealer.set_reveal_child(False) self.info_revealer.set_reveal_child(False)
@ -159,7 +146,6 @@ class CoverPanel(Gtk.Overlay):
self.info_revealer.set_reveal_child(True) self.info_revealer.set_reveal_child(True)
GObject.idle_add(self._resize_image) GObject.idle_add(self._resize_image)
def set_albumart(self, album, data): def set_albumart(self, album, data):
if album == self._current_album: if album == self._current_album:
if data: if data:
@ -177,7 +163,6 @@ class CoverPanel(Gtk.Overlay):
# Show image # Show image
GObject.idle_add(self._show_image) GObject.idle_add(self._show_image)
def _set_tracks(self, album): def _set_tracks(self, album):
self.songs_scale.clear_marks() self.songs_scale.clear_marks()
self.songs_scale.set_range(0, album.get_length()) self.songs_scale.set_range(0, album.get_length())
@ -187,19 +172,12 @@ class CoverPanel(Gtk.Overlay):
if length > 0 and length < album.get_length(): if length > 0 and length < album.get_length():
cur_length = cur_length + 1 cur_length = cur_length + 1
self.songs_scale.add_mark( self.songs_scale.add_mark(
cur_length, cur_length, Gtk.PositionType.RIGHT,
Gtk.PositionType.RIGHT, GObject.markup_escape_text(Utils.create_track_title(track)))
GObject.markup_escape_text(
Utils.create_track_title(track)
)
)
length = length + track.get_length() length = length + track.get_length()
self.songs_scale.add_mark( self.songs_scale.add_mark(
length, length, Gtk.PositionType.RIGHT,
Gtk.PositionType.RIGHT, "{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60)))
"{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60))
)
def _enable_tracklist(self): def _enable_tracklist(self):
if self._current_album: if self._current_album:
@ -210,14 +188,12 @@ class CoverPanel(Gtk.Overlay):
# disable # disable
self.info_revealer.set_reveal_child(False) self.info_revealer.set_reveal_child(False)
def _playing(self): def _playing(self):
value = self.songs_scale.get_value() + 1 value = self.songs_scale.get_value() + 1
self.songs_scale.set_value(value) self.songs_scale.set_value(value)
return True return True
def _show_image(self): def _show_image(self):
if self._cover_pixbuf: if self._cover_pixbuf:
self._resize_image() self._resize_image()
@ -226,7 +202,6 @@ class CoverPanel(Gtk.Overlay):
self.cover_stack.set_visible_child(self.cover_default) self.cover_stack.set_visible_child(self.cover_default)
self.cover_spinner.stop() self.cover_spinner.stop()
def _resize_image(self): def _resize_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
@ -239,7 +214,10 @@ class CoverPanel(Gtk.Overlay):
current_width, current_height = self._current_size current_width, current_height = self._current_size
if size_width == current_width and size_height == current_height: if size_width == current_width and size_height == current_height:
return return
self._current_size = (size_width, size_height,) self._current_size = (
size_width,
size_height,
)
# Get pixelbuffer # Get pixelbuffer
pixbuf = self._cover_pixbuf pixbuf = self._cover_pixbuf
@ -254,9 +232,10 @@ class CoverPanel(Gtk.Overlay):
ratio = min(ratioW, ratioH) ratio = min(ratioW, ratioH)
ratio = min(ratio, 1) ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen # Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width()*ratio)) width = int(math.floor(pixbuf.get_width() * ratio))
height = int(math.floor(pixbuf.get_height()*ratio)) height = int(math.floor(pixbuf.get_height() * ratio))
if width <= 0 or height <= 0: if width <= 0 or height <= 0:
return return
self.cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) self.cover_image.set_from_pixbuf(
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.cover_image.show() self.cover_image.show()

View file

@ -1,16 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import locale import locale
import logging import logging
import math import math
import threading import threading
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gdk, GObject, GdkPixbuf, Gio, Adw from gi.repository import Gtk, Gdk, GObject, GdkPixbuf, Gio, Adw
from mcg import client from mcg import client
from mcg.albumheaderbar import AlbumHeaderbar from mcg.albumheaderbar import AlbumHeaderbar
from mcg.utils import SortOrder from mcg.utils import SortOrder
@ -19,8 +17,6 @@ from mcg.utils import GridItem
from mcg.utils import SearchFilter from mcg.utils import SearchFilter
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/library-panel.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/library-panel.ui')
class LibraryPanel(Adw.Bin): class LibraryPanel(Adw.Bin):
__gtype_name__ = 'McgLibraryPanel' __gtype_name__ = 'McgLibraryPanel'
@ -28,16 +24,16 @@ class LibraryPanel(Adw.Bin):
'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()), 'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()), 'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'update': (GObject.SIGNAL_RUN_FIRST, None, ()), 'update': (GObject.SIGNAL_RUN_FIRST, None, ()),
'play': (GObject.SIGNAL_RUN_FIRST, None, (str,)), 'play': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
'queue': (GObject.SIGNAL_RUN_FIRST, None, (str,)), 'queue': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
'queue-multiple': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), 'queue-multiple':
'item-size-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)), (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
'sort-order-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)), 'item-size-changed': (GObject.SIGNAL_RUN_FIRST, None, (int, )),
'sort-type-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)), 'sort-order-changed': (GObject.SIGNAL_RUN_FIRST, None, (int, )),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str,)), 'sort-type-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool, )),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
} }
# Widgets # Widgets
library_stack = Gtk.Template.Child() library_stack = Gtk.Template.Child()
panel_normal = Gtk.Template.Child() panel_normal = Gtk.Template.Child()
@ -74,7 +70,6 @@ class LibraryPanel(Adw.Bin):
standalone_scroll = Gtk.Template.Child() standalone_scroll = Gtk.Template.Child()
standalone_image = Gtk.Template.Child() standalone_image = Gtk.Template.Child()
def __init__(self, client, **kwargs): def __init__(self, client, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self._logger = logging.getLogger(__name__) self._logger = logging.getLogger(__name__)
@ -90,7 +85,8 @@ class LibraryPanel(Adw.Bin):
self._old_ranges = {} self._old_ranges = {}
self._library_lock = threading.Lock() self._library_lock = threading.Lock()
self._library_stop = threading.Event() self._library_stop = threading.Event()
self._icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()) self._icon_theme = Gtk.IconTheme.get_for_display(
Gdk.Display.get_default())
self._standalone_pixbuf = None self._standalone_pixbuf = None
self._selected_albums = [] self._selected_albums = []
self._is_selected = False self._is_selected = False
@ -98,13 +94,16 @@ class LibraryPanel(Adw.Bin):
# Widgets # Widgets
# Header bar # Header bar
self._headerbar_standalone = AlbumHeaderbar() self._headerbar_standalone = AlbumHeaderbar()
self._headerbar_standalone.connect('close', self.on_standalone_close_clicked) self._headerbar_standalone.connect('close',
self.on_standalone_close_clicked)
# Library Grid: Model # Library Grid: Model
self._library_grid_model = Gio.ListStore() self._library_grid_model = Gio.ListStore()
self._library_grid_filter = Gtk.FilterListModel() self._library_grid_filter = Gtk.FilterListModel()
self._library_grid_filter.set_model(self._library_grid_model) self._library_grid_filter.set_model(self._library_grid_model)
self._library_grid_selection_multi = Gtk.MultiSelection.new(self._library_grid_filter) self._library_grid_selection_multi = Gtk.MultiSelection.new(
self._library_grid_selection_single = Gtk.SingleSelection.new(self._library_grid_filter) self._library_grid_filter)
self._library_grid_selection_single = Gtk.SingleSelection.new(
self._library_grid_filter)
# Library Grid # Library Grid
self.library_grid.set_model(self._library_grid_selection_single) self.library_grid.set_model(self._library_grid_selection_single)
# Toolbar menu # Toolbar menu
@ -118,41 +117,38 @@ class LibraryPanel(Adw.Bin):
# Button controller for grid scale # Button controller for grid scale
buttonController = Gtk.GestureClick() buttonController = Gtk.GestureClick()
buttonController.connect('unpaired-release', self.on_grid_scale_released) buttonController.connect('unpaired-release',
self.on_grid_scale_released)
self.grid_scale.add_controller(buttonController) self.grid_scale.add_controller(buttonController)
def get_headerbar_standalone(self): def get_headerbar_standalone(self):
return self._headerbar_standalone return self._headerbar_standalone
def get_toolbar(self): def get_toolbar(self):
return self.toolbar return self.toolbar
def set_selected(self, selected): def set_selected(self, selected):
self._is_selected = selected self._is_selected = selected
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_select_toggled(self, widget): def on_select_toggled(self, widget):
if self.select_button.get_active(): if self.select_button.get_active():
self.actionbar_revealer.set_reveal_child(True) self.actionbar_revealer.set_reveal_child(True)
self.library_grid.set_model(self._library_grid_selection_multi) self.library_grid.set_model(self._library_grid_selection_multi)
self.library_grid.set_single_click_activate(False) self.library_grid.set_single_click_activate(False)
self.library_grid.get_style_context().add_class(Utils.CSS_SELECTION) self.library_grid.get_style_context().add_class(
Utils.CSS_SELECTION)
else: else:
self.actionbar_revealer.set_reveal_child(False) self.actionbar_revealer.set_reveal_child(False)
self.library_grid.set_model(self._library_grid_selection_single) self.library_grid.set_model(self._library_grid_selection_single)
self.library_grid.set_single_click_activate(True) self.library_grid.set_single_click_activate(True)
self.library_grid.get_style_context().remove_class(Utils.CSS_SELECTION) self.library_grid.get_style_context().remove_class(
Utils.CSS_SELECTION)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_update_clicked(self, widget): def on_update_clicked(self, widget):
self.emit('update') self.emit('update')
def on_grid_scale_released(self, widget, x, y, npress, sequence): def on_grid_scale_released(self, widget, x, y, npress, sequence):
size = math.floor(self.grid_scale.get_value()) size = math.floor(self.grid_scale.get_value())
range = self.grid_scale.get_adjustment() range = self.grid_scale.get_adjustment()
@ -163,7 +159,6 @@ class LibraryPanel(Adw.Bin):
self._redraw() self._redraw()
GObject.idle_add(self.toolbar_popover.popdown) GObject.idle_add(self.toolbar_popover.popdown)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_grid_scale_changed(self, widget): def on_grid_scale_changed(self, widget):
size = math.floor(self.grid_scale.get_value()) size = math.floor(self.grid_scale.get_value())
@ -172,15 +167,16 @@ class LibraryPanel(Adw.Bin):
return return
self._set_widget_grid_size(self.library_grid, size, True) self._set_widget_grid_size(self.library_grid, size, True)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_sort_toggled(self, widget): def on_sort_toggled(self, widget):
if widget.get_active(): if widget.get_active():
self._sort_order = [key for key, value in self._toolbar_sort_buttons.items() if value is widget][0] self._sort_order = [
key for key, value in self._toolbar_sort_buttons.items()
if value is widget
][0]
self._sort_grid_model() self._sort_grid_model()
self.emit('sort-order-changed', self._sort_order) self.emit('sort-order-changed', self._sort_order)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_sort_order_toggled(self, button): def on_sort_order_toggled(self, button):
if button.get_active(): if button.get_active():
@ -190,16 +186,14 @@ class LibraryPanel(Adw.Bin):
self._sort_grid_model() self._sort_grid_model()
self.emit('sort-type-changed', button.get_active()) self.emit('sort-type-changed', button.get_active())
def set_size(self, width, height): def set_size(self, width, height):
self._set_marks() self._set_marks()
self._resize_standalone_image() self._resize_standalone_image()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_filter_entry_changed(self, widget): def on_filter_entry_changed(self, widget):
self._library_grid_filter.set_filter(SearchFilter(self.filter_entry.get_text())) self._library_grid_filter.set_filter(
SearchFilter(self.filter_entry.get_text()))
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_library_grid_clicked(self, widget, position): def on_library_grid_clicked(self, widget, position):
@ -222,84 +216,80 @@ class LibraryPanel(Adw.Bin):
self.standalone_stack.set_visible_child(self.standalone_spinner) self.standalone_stack.set_visible_child(self.standalone_spinner)
self.standalone_spinner.start() self.standalone_spinner.start()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_selection_cancel_clicked(self, widget): def on_selection_cancel_clicked(self, widget):
self.select_button.set_active(False) self.select_button.set_active(False)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_selection_add_clicked(self, widget): def on_selection_add_clicked(self, widget):
self.emit('queue-multiple', self._get_selected_albums()) self.emit('queue-multiple', self._get_selected_albums())
self.select_button.set_active(False) self.select_button.set_active(False)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_standalone_play_clicked(self, widget): def on_standalone_play_clicked(self, widget):
self.emit('play', self._selected_albums[0].get_id()) self.emit('play', self._selected_albums[0].get_id())
self._close_standalone() self._close_standalone()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_standalone_queue_clicked(self, widget): def on_standalone_queue_clicked(self, widget):
self.emit('queue', self._selected_albums[0].get_id()) self.emit('queue', self._selected_albums[0].get_id())
self._close_standalone() self._close_standalone()
def on_standalone_close_clicked(self, widget): def on_standalone_close_clicked(self, widget):
self._close_standalone() self._close_standalone()
def show_search(self): def show_search(self):
self.filter_bar.set_search_mode(True) self.filter_bar.set_search_mode(True)
def set_item_size(self, item_size): def set_item_size(self, item_size):
if self._item_size != item_size: if self._item_size != item_size:
self._item_size = item_size self._item_size = item_size
self.grid_scale.set_value(item_size) self.grid_scale.set_value(item_size)
self._redraw() self._redraw()
def get_item_size(self): def get_item_size(self):
return self._item_size return self._item_size
def set_sort_order(self, sort): def set_sort_order(self, sort):
button = self._toolbar_sort_buttons[sort] button = self._toolbar_sort_buttons[sort]
if button: if button:
self._sort_order = [key for key, value in self._toolbar_sort_buttons.items() if value is button][0] self._sort_order = [
key for key, value in self._toolbar_sort_buttons.items()
if value is button
][0]
if not button.get_active(): if not button.get_active():
button.set_active(True) button.set_active(True)
self._sort_grid_model() self._sort_grid_model()
def set_sort_type(self, sort_type): def set_sort_type(self, sort_type):
sort_type_gtk = Gtk.SortType.DESCENDING if sort_type else Gtk.SortType.ASCENDING if sort_type:
sort_type_gtk = Gtk.SortType.DESCENDING
else:
sort_type_gtk = Gtk.SortType.ASCENDING
if sort_type_gtk != self._sort_type: if sort_type_gtk != self._sort_type:
self._sort_type = sort_type_gtk self._sort_type = sort_type_gtk
self.toolbar_sort_order_button.set_active(sort_type) self.toolbar_sort_order_button.set_active(sort_type)
self._sort_grid_model() self._sort_grid_model()
def get_sort_type(self): def get_sort_type(self):
return (self._sort_type != Gtk.SortType.ASCENDING) return (self._sort_type != Gtk.SortType.ASCENDING)
def init_albums(self): def init_albums(self):
self.progress_bar.set_text(locale.gettext("Loading albums")) self.progress_bar.set_text(locale.gettext("Loading albums"))
def load_albums(self): def load_albums(self):
self.progress_bar.pulse() self.progress_bar.pulse()
def set_albums(self, host, albums): def set_albums(self, host, albums):
self._host = host self._host = host
self._library_stop.set() self._library_stop.set()
threading.Thread(target=self._set_albums, args=(host, albums, self._item_size,)).start() threading.Thread(target=self._set_albums,
args=(
host,
albums,
self._item_size,
)).start()
def set_albumart(self, album, data): def set_albumart(self, album, data):
if album in self._selected_albums: if album in self._selected_albums:
@ -315,24 +305,25 @@ class LibraryPanel(Adw.Bin):
# Show image # Show image
GObject.idle_add(self._show_image) GObject.idle_add(self._show_image)
def _sort_grid_model(self): def _sort_grid_model(self):
GObject.idle_add(self._library_grid_model.sort, self._grid_model_compare_func, self._sort_order, self._sort_type) GObject.idle_add(self._library_grid_model.sort,
self._grid_model_compare_func, self._sort_order,
self._sort_type)
def _grid_model_compare_func(self, item1, item2, criterion, order): def _grid_model_compare_func(self, item1, item2, criterion, order):
return client.MCGAlbum.compare(item1.get_album(), item2.get_album(), criterion, (order == Gtk.SortType.DESCENDING)) return client.MCGAlbum.compare(item1.get_album(), item2.get_album(),
criterion,
(order == Gtk.SortType.DESCENDING))
def stop_threads(self): def stop_threads(self):
self._library_stop.set() self._library_stop.set()
def _set_albums(self, host, albums, size): def _set_albums(self, host, albums, size):
self._library_lock.acquire() self._library_lock.acquire()
self._albums = albums self._albums = albums
stack_transition_type = self.stack.get_transition_type() stack_transition_type = self.stack.get_transition_type()
GObject.idle_add(self.stack.set_transition_type, Gtk.StackTransitionType.NONE) GObject.idle_add(self.stack.set_transition_type,
Gtk.StackTransitionType.NONE)
GObject.idle_add(self.stack.set_visible_child, self.progress_box) GObject.idle_add(self.stack.set_visible_child, self.progress_box)
GObject.idle_add(self.progress_bar.set_fraction, 0.0) GObject.idle_add(self.progress_bar.set_fraction, 0.0)
GObject.idle_add(self.stack.set_transition_type, stack_transition_type) GObject.idle_add(self.stack.set_transition_type, stack_transition_type)
@ -354,30 +345,31 @@ class LibraryPanel(Adw.Bin):
self._logger.exception("Failed to load albumart", e) self._logger.exception("Failed to load albumart", e)
if pixbuf is None: if pixbuf is None:
pixbuf = self._icon_theme.lookup_icon( pixbuf = self._icon_theme.lookup_icon(
Utils.STOCK_ICON_DEFAULT, Utils.STOCK_ICON_DEFAULT, None, self._item_size,
None, self._item_size, Gtk.TextDirection.LTR,
self._item_size, Gtk.IconLookupFlags.FORCE_SYMBOLIC)
self._item_size,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC
)
if pixbuf is not None: if pixbuf is not None:
self._grid_pixbufs[album.get_id()] = pixbuf self._grid_pixbufs[album.get_id()] = pixbuf
GObject.idle_add(self._library_grid_model.append, GridItem(album, pixbuf)) GObject.idle_add(self._library_grid_model.append,
GridItem(album, pixbuf))
i += 1 i += 1
GObject.idle_add(self.progress_bar.set_fraction, i/n) GObject.idle_add(self.progress_bar.set_fraction, i / n)
GObject.idle_add(self.progress_bar.set_text, locale.gettext("Loading images")) GObject.idle_add(self.progress_bar.set_text,
locale.gettext("Loading images"))
self._library_lock.release() self._library_lock.release()
GObject.idle_add(self.stack.set_visible_child, self.scroll) GObject.idle_add(self.stack.set_visible_child, self.scroll)
self._sort_grid_model() self._sort_grid_model()
def _set_widget_grid_size(self, grid_widget, size, vertical): def _set_widget_grid_size(self, grid_widget, size, vertical):
self._library_stop.set() self._library_stop.set()
threading.Thread(target=self._set_widget_grid_size_thread, args=(grid_widget, size, vertical,)).start() threading.Thread(target=self._set_widget_grid_size_thread,
args=(
grid_widget,
size,
vertical,
)).start()
def _set_widget_grid_size_thread(self, grid_widget, size, vertical): def _set_widget_grid_size_thread(self, grid_widget, size, vertical):
self._library_lock.acquire() self._library_lock.acquire()
@ -391,16 +383,12 @@ class LibraryPanel(Adw.Bin):
pixbuf = self._grid_pixbufs[album_id] pixbuf = self._grid_pixbufs[album_id]
if pixbuf is not None: if pixbuf is not None:
pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.NEAREST) pixbuf = pixbuf.scale_simple(size, size,
GdkPixbuf.InterpType.NEAREST)
else: else:
pixbuf = self._icon_theme.lookup_icon( pixbuf = self._icon_theme.lookup_icon(
Utils.STOCK_ICON_DEFAULT, Utils.STOCK_ICON_DEFAULT, None, size, size,
None, Gtk.TextDirection.LTR, Gtk.IconLookupFlags.FORCE_SYMBOLIC)
size,
size,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC
)
GObject.idle_add(grid_item.set_cover, pixbuf) GObject.idle_add(grid_item.set_cover, pixbuf)
if self._library_stop.is_set(): if self._library_stop.is_set():
@ -409,18 +397,15 @@ class LibraryPanel(Adw.Bin):
self._library_lock.release() self._library_lock.release()
def _show_image(self): def _show_image(self):
self._resize_standalone_image() self._resize_standalone_image()
self.standalone_stack.set_visible_child(self.standalone_scroll) self.standalone_stack.set_visible_child(self.standalone_scroll)
self.standalone_spinner.stop() self.standalone_spinner.stop()
def _redraw(self): def _redraw(self):
if self._albums is not None: if self._albums is not None:
self.set_albums(self._host, self._albums) self.set_albums(self._host, self._albums)
def _set_marks(self): def _set_marks(self):
width = self.scroll.get_width() width = self.scroll.get_width()
if width == self._grid_width: if width == self._grid_width:
@ -435,23 +420,16 @@ class LibraryPanel(Adw.Bin):
for index in range(countMin, countMax): for index in range(countMin, countMax):
pixel = int(width / index) pixel = int(width / index)
pixel = pixel - (2 * int(pixel / 100)) pixel = pixel - (2 * int(pixel / 100))
self.grid_scale.add_mark( self.grid_scale.add_mark(pixel, Gtk.PositionType.BOTTOM, None)
pixel,
Gtk.PositionType.BOTTOM,
None
)
def _open_standalone(self): def _open_standalone(self):
self.library_stack.set_visible_child(self.panel_standalone) self.library_stack.set_visible_child(self.panel_standalone)
self.emit('open-standalone') self.emit('open-standalone')
def _close_standalone(self): def _close_standalone(self):
self.library_stack.set_visible_child(self.panel_normal) self.library_stack.set_visible_child(self.panel_normal)
self.emit('close-standalone') self.emit('close-standalone')
def _resize_standalone_image(self): def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
@ -473,29 +451,24 @@ class LibraryPanel(Adw.Bin):
ratio = min(ratioW, ratioH) ratio = min(ratioW, ratioH)
ratio = min(ratio, 1) ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen # Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width()*ratio)) width = int(math.floor(pixbuf.get_width() * ratio))
height = int(math.floor(pixbuf.get_height()*ratio)) height = int(math.floor(pixbuf.get_height() * ratio))
if width <= 0 or height <= 0: if width <= 0 or height <= 0:
return return
# Pixelpuffer auf Oberfläche zeichnen # Pixelpuffer auf Oberfläche zeichnen
self.standalone_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) self.standalone_image.set_from_pixbuf(
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.standalone_image.show() self.standalone_image.show()
def _get_default_image(self): def _get_default_image(self):
return self._icon_theme.lookup_icon( return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None,
Utils.STOCK_ICON_DEFAULT, 512, 512, Gtk.TextDirection.LTR,
None, Gtk.IconLookupFlags.FORCE_SYMBOLIC)
512,
512,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC
)
def _get_selected_albums(self): def _get_selected_albums(self):
albums = [] albums = []
for i in range(self.library_grid.get_model().get_n_items()): for i in range(self.library_grid.get_model().get_n_items()):
if self.library_grid.get_model().is_selected(i): if self.library_grid.get_model().is_selected(i):
albums.append(self.library_grid.get_model().get_item(i).get_album().get_id()) albums.append(self.library_grid.get_model().get_item(
i).get_album().get_id())
return albums return albums

View file

@ -3,7 +3,6 @@ import sys
from .application import Application from .application import Application
def main(version): def main(version):
app = Application() app = Application()
return app.run(sys.argv) return app.run(sys.argv)

View file

@ -1,23 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
import logging
import math import math
import threading import threading
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gdk, Gio, GObject, GdkPixbuf, Adw from gi.repository import Gtk, Gdk, Gio, GObject, GdkPixbuf, Adw
from mcg import client from mcg import client
from mcg.albumheaderbar import AlbumHeaderbar from mcg.albumheaderbar import AlbumHeaderbar
from mcg.utils import Utils from mcg.utils import Utils
from mcg.utils import GridItem from mcg.utils import GridItem
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/playlist-panel.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/playlist-panel.ui')
class PlaylistPanel(Adw.Bin): class PlaylistPanel(Adw.Bin):
__gtype_name__ = 'McgPlaylistPanel' __gtype_name__ = 'McgPlaylistPanel'
@ -25,13 +20,14 @@ class PlaylistPanel(Adw.Bin):
'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()), 'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()), 'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'clear-playlist': (GObject.SIGNAL_RUN_FIRST, None, ()), 'clear-playlist': (GObject.SIGNAL_RUN_FIRST, None, ()),
'remove-album': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), 'remove-album':
'remove-multiple-albums': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
'play': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), 'remove-multiple-albums':
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str,)), (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 # Widgets
playlist_stack = Gtk.Template.Child() playlist_stack = Gtk.Template.Child()
panel_normal = Gtk.Template.Child() panel_normal = Gtk.Template.Child()
@ -52,7 +48,6 @@ class PlaylistPanel(Adw.Bin):
standalone_scroll = Gtk.Template.Child() standalone_scroll = Gtk.Template.Child()
standalone_image = Gtk.Template.Child() standalone_image = Gtk.Template.Child()
def __init__(self, client, **kwargs): def __init__(self, client, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self._client = client self._client = client
@ -62,7 +57,8 @@ class PlaylistPanel(Adw.Bin):
self._playlist_albums = None self._playlist_albums = None
self._playlist_lock = threading.Lock() self._playlist_lock = threading.Lock()
self._playlist_stop = threading.Event() self._playlist_stop = threading.Event()
self._icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()) self._icon_theme = Gtk.IconTheme.get_for_display(
Gdk.Display.get_default())
self._standalone_pixbuf = None self._standalone_pixbuf = None
self._selected_albums = [] self._selected_albums = []
self._is_selected = False self._is_selected = False
@ -70,46 +66,45 @@ class PlaylistPanel(Adw.Bin):
# Widgets # Widgets
# Header bar # Header bar
self._headerbar_standalone = AlbumHeaderbar() self._headerbar_standalone = AlbumHeaderbar()
self._headerbar_standalone.connect('close', self.on_headerbar_close_clicked) self._headerbar_standalone.connect('close',
self.on_headerbar_close_clicked)
# Playlist Grid: Model # Playlist Grid: Model
self._playlist_grid_model = Gio.ListStore() self._playlist_grid_model = Gio.ListStore()
self._playlist_grid_selection_multi = Gtk.MultiSelection.new(self._playlist_grid_model) self._playlist_grid_selection_multi = Gtk.MultiSelection.new(
self._playlist_grid_selection_single = Gtk.SingleSelection.new(self._playlist_grid_model) self._playlist_grid_model)
self._playlist_grid_selection_single = Gtk.SingleSelection.new(
self._playlist_grid_model)
# Playlist Grid # Playlist Grid
self.playlist_grid.set_model(self._playlist_grid_selection_single) self.playlist_grid.set_model(self._playlist_grid_selection_single)
def get_headerbar_standalone(self): def get_headerbar_standalone(self):
return self._headerbar_standalone return self._headerbar_standalone
def get_toolbar(self): def get_toolbar(self):
return self.toolbar return self.toolbar
def set_selected(self, selected): def set_selected(self, selected):
self._is_selected = selected self._is_selected = selected
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_select_toggled(self, widget): def on_select_toggled(self, widget):
if self.select_button.get_active(): if self.select_button.get_active():
self.actionbar_revealer.set_reveal_child(True) self.actionbar_revealer.set_reveal_child(True)
self.playlist_grid.set_model(self._playlist_grid_selection_multi) self.playlist_grid.set_model(self._playlist_grid_selection_multi)
self.playlist_grid.set_single_click_activate(False) self.playlist_grid.set_single_click_activate(False)
self.playlist_grid.get_style_context().add_class(Utils.CSS_SELECTION) self.playlist_grid.get_style_context().add_class(
Utils.CSS_SELECTION)
else: else:
self.actionbar_revealer.set_reveal_child(False) self.actionbar_revealer.set_reveal_child(False)
self.playlist_grid.set_model(self._playlist_grid_selection_single) self.playlist_grid.set_model(self._playlist_grid_selection_single)
self.playlist_grid.set_single_click_activate(True) self.playlist_grid.set_single_click_activate(True)
self.playlist_grid.get_style_context().remove_class(Utils.CSS_SELECTION) self.playlist_grid.get_style_context().remove_class(
Utils.CSS_SELECTION)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_clear_clicked(self, widget): def on_clear_clicked(self, widget):
self.emit('clear-playlist') self.emit('clear-playlist')
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_playlist_grid_clicked(self, widget, position): def on_playlist_grid_clicked(self, widget, position):
# Get selected album # Get selected album
@ -131,53 +126,48 @@ class PlaylistPanel(Adw.Bin):
self.standalone_stack.set_visible_child(self.standalone_spinner) self.standalone_stack.set_visible_child(self.standalone_spinner)
self.standalone_spinner.start() self.standalone_spinner.start()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_selection_cancel_clicked(self, widget): def on_selection_cancel_clicked(self, widget):
self.select_button.set_active(False) self.select_button.set_active(False)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_selection_remove_clicked(self, widget): def on_selection_remove_clicked(self, widget):
self.emit('remove-multiple-albums', self._get_selected_albums()) self.emit('remove-multiple-albums', self._get_selected_albums())
self.select_button.set_active(False) self.select_button.set_active(False)
def on_headerbar_close_clicked(self, widget): def on_headerbar_close_clicked(self, widget):
self._close_standalone() self._close_standalone()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_standalone_remove_clicked(self, widget): def on_standalone_remove_clicked(self, widget):
self.emit('remove-album', self._get_selected_albums()[0]) self.emit('remove-album', self._get_selected_albums()[0])
self._close_standalone() self._close_standalone()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_standalone_play_clicked(self, widget): def on_standalone_play_clicked(self, widget):
self.emit('play', self._get_selected_albums()[0]) self.emit('play', self._get_selected_albums()[0])
self._close_standalone() self._close_standalone()
def set_size(self, width, height): def set_size(self, width, height):
self._resize_standalone_image() self._resize_standalone_image()
def set_item_size(self, item_size): def set_item_size(self, item_size):
if self._item_size != item_size: if self._item_size != item_size:
self._item_size = item_size self._item_size = item_size
self._redraw() self._redraw()
def get_item_size(self): def get_item_size(self):
return self._item_size return self._item_size
def set_playlist(self, host, playlist): def set_playlist(self, host, playlist):
self._host = host self._host = host
self._playlist_stop.set() self._playlist_stop.set()
threading.Thread(target=self._set_playlist, args=(host, playlist, self._item_size,)).start() threading.Thread(target=self._set_playlist,
args=(
host,
playlist,
self._item_size,
)).start()
def set_albumart(self, album, data): def set_albumart(self, album, data):
if album in self._selected_albums: if album in self._selected_albums:
@ -193,11 +183,9 @@ class PlaylistPanel(Adw.Bin):
# Show image # Show image
GObject.idle_add(self._show_image) GObject.idle_add(self._show_image)
def stop_threads(self): def stop_threads(self):
self._playlist_stop.set() self._playlist_stop.set()
def _set_playlist(self, host, playlist, size): def _set_playlist(self, host, playlist, size):
self._playlist_lock.acquire() self._playlist_lock.acquire()
self._playlist_stop.clear() self._playlist_stop.clear()
@ -220,13 +208,9 @@ class PlaylistPanel(Adw.Bin):
self._logger.exception("Failed to load albumart") self._logger.exception("Failed to load albumart")
if pixbuf is None: if pixbuf is None:
pixbuf = self._icon_theme.lookup_icon( pixbuf = self._icon_theme.lookup_icon(
Utils.STOCK_ICON_DEFAULT, Utils.STOCK_ICON_DEFAULT, None, self._item_size,
None, self._item_size, Gtk.TextDirection.LTR,
self._item_size, Gtk.IconLookupFlags.FORCE_SYMBOLIC)
self._item_size,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC
)
if pixbuf is not None: if pixbuf is not None:
self._playlist_grid_model.append(GridItem(album, pixbuf)) self._playlist_grid_model.append(GridItem(album, pixbuf))
@ -237,28 +221,23 @@ class PlaylistPanel(Adw.Bin):
self.playlist_grid.set_model(self._playlist_grid_selection_single) self.playlist_grid.set_model(self._playlist_grid_selection_single)
self._playlist_lock.release() self._playlist_lock.release()
def _show_image(self): def _show_image(self):
self._resize_standalone_image() self._resize_standalone_image()
self.standalone_stack.set_visible_child(self.standalone_scroll) self.standalone_stack.set_visible_child(self.standalone_scroll)
self.standalone_spinner.stop() self.standalone_spinner.stop()
def _redraw(self): def _redraw(self):
if self._playlist is not None: if self._playlist is not None:
self.set_playlist(self._host, self._playlist) self.set_playlist(self._host, self._playlist)
def _open_standalone(self): def _open_standalone(self):
self.playlist_stack.set_visible_child(self.panel_standalone) self.playlist_stack.set_visible_child(self.panel_standalone)
self.emit('open-standalone') self.emit('open-standalone')
def _close_standalone(self): def _close_standalone(self):
self.playlist_stack.set_visible_child(self.panel_normal) self.playlist_stack.set_visible_child(self.panel_normal)
self.emit('close-standalone') self.emit('close-standalone')
def _resize_standalone_image(self): def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
@ -280,29 +259,24 @@ class PlaylistPanel(Adw.Bin):
ratio = min(ratioW, ratioH) ratio = min(ratioW, ratioH)
ratio = min(ratio, 1) ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen # Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width()*ratio)) width = int(math.floor(pixbuf.get_width() * ratio))
height = int(math.floor(pixbuf.get_height()*ratio)) height = int(math.floor(pixbuf.get_height() * ratio))
if width <= 0 or height <= 0: if width <= 0 or height <= 0:
return return
# Pixelpuffer auf Oberfläche zeichnen # Pixelpuffer auf Oberfläche zeichnen
self.standalone_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) self.standalone_image.set_from_pixbuf(
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.standalone_image.show() self.standalone_image.show()
def _get_default_image(self): def _get_default_image(self):
return self._icon_theme.lookup_icon( return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None,
Utils.STOCK_ICON_DEFAULT, 512, 512, Gtk.TextDirection.LTR,
None, Gtk.IconLookupFlags.FORCE_SYMBOLIC)
512,
512,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC
)
def _get_selected_albums(self): def _get_selected_albums(self):
albums = [] albums = []
for i in range(self.playlist_grid.get_model().get_n_items()): for i in range(self.playlist_grid.get_model().get_n_items()):
if self.playlist_grid.get_model().is_selected(i): if self.playlist_grid.get_model().is_selected(i):
albums.append(self.playlist_grid.get_model().get_item(i).get_album()) albums.append(
self.playlist_grid.get_model().get_item(i).get_album())
return albums return albums

View file

@ -1,20 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GObject from gi.repository import Gtk, Adw, GObject
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/server-panel.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/server-panel.ui')
class ServerPanel(Adw.Bin): class ServerPanel(Adw.Bin):
__gtype_name__ = 'McgServerPanel' __gtype_name__ = 'McgServerPanel'
__gsignals__ = { __gsignals__ = {
'change-output-device': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,bool,)), 'change-output-device': (GObject.SIGNAL_RUN_FIRST, None, (
GObject.TYPE_PYOBJECT,
bool,
)),
} }
# Widgets # Widgets
@ -34,7 +34,6 @@ class ServerPanel(Adw.Bin):
# Audio ouptut devices widgets # Audio ouptut devices widgets
output_devices = Gtk.Template.Child() output_devices = Gtk.Template.Child()
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self._none_label = "" self._none_label = ""
@ -44,19 +43,15 @@ class ServerPanel(Adw.Bin):
# Widgets # Widgets
self._none_label = self.status_file.get_label() self._none_label = self.status_file.get_label()
def set_selected(self, selected): def set_selected(self, selected):
self._is_selected = selected self._is_selected = selected
def get_toolbar(self): def get_toolbar(self):
return self.toolbar return self.toolbar
def on_output_device_toggled(self, widget, device): def on_output_device_toggled(self, widget, device):
self.emit('change-output-device', device, widget.get_active()) self.emit('change-output-device', device, widget.get_active())
def set_status(self, file, audio, bitrate, error): def set_status(self, file, audio, bitrate, error):
if file: if file:
file = GObject.markup_escape_text(file) file = GObject.markup_escape_text(file)
@ -64,10 +59,11 @@ class ServerPanel(Adw.Bin):
file = self._none_label file = self._none_label
self.status_file.set_markup(file) self.status_file.set_markup(file)
# Audio information # Audio information
if audio: if audio:
parts = audio.split(":") parts = audio.split(":")
if len(parts) == 3: if len(parts) == 3:
audio = "{}Hz, {}bit, {}channels".format(parts[0], parts[1], parts[2]) audio = "{}Hz, {}bit, {}channels".format(
parts[0], parts[1], parts[2])
else: else:
audio = self._none_label audio = self._none_label
self.status_audio.set_markup(audio) self.status_audio.set_markup(audio)
@ -84,7 +80,6 @@ class ServerPanel(Adw.Bin):
error = self._none_label error = self._none_label
self.status_error.set_markup(error) self.status_error.set_markup(error)
def set_stats(self, artists, albums, songs, dbplaytime, playtime, uptime): def set_stats(self, artists, albums, songs, dbplaytime, playtime, uptime):
self.stats_artists.set_text(str(artists)) self.stats_artists.set_text(str(artists))
self.stats_albums.set_text(str(albums)) self.stats_albums.set_text(str(albums))
@ -93,7 +88,6 @@ class ServerPanel(Adw.Bin):
self.stats_playtime.set_text(str(playtime)) self.stats_playtime.set_text(str(playtime))
self.stats_uptime.set_text(str(uptime)) self.stats_uptime.set_text(str(uptime))
def set_output_devices(self, devices): def set_output_devices(self, devices):
device_ids = [] device_ids = []
@ -102,17 +96,21 @@ class ServerPanel(Adw.Bin):
device_ids.append(device.get_id()) device_ids.append(device.get_id())
if device.get_id() in self._output_buttons.keys(): if device.get_id() in self._output_buttons.keys():
self._output_buttons[device.get_id()].freeze_notify() self._output_buttons[device.get_id()].freeze_notify()
self._output_buttons[device.get_id()].set_active(device.is_enabled()) self._output_buttons[device.get_id()].set_active(
device.is_enabled())
self._output_buttons[device.get_id()].thaw_notify() self._output_buttons[device.get_id()].thaw_notify()
else: else:
button = Gtk.CheckButton.new_with_label(device.get_name()) button = Gtk.CheckButton.new_with_label(device.get_name())
if device.is_enabled(): if device.is_enabled():
button.set_active(True) button.set_active(True)
handler = button.connect('toggled', self.on_output_device_toggled, device) handler = button.connect('toggled',
self.on_output_device_toggled, device)
self.output_devices.insert(button, -1) self.output_devices.insert(button, -1)
self._output_buttons[device.get_id()] = button self._output_buttons[device.get_id()] = button
# Remove devices # Remove devices
for id in self._output_buttons.keys(): for id in self._output_buttons.keys():
if id not in device_ids: if id not in device_ids:
self.output_devices.remove(self._output_buttons[id].get_parent()) self.output_devices.remove(
self._output_buttons[id].get_parent())

View file

@ -1,18 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
from gi.repository import Gtk from gi.repository import Gtk
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/shortcuts-dialog.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/shortcuts-dialog.ui')
class ShortcutsDialog(Gtk.ShortcutsWindow): class ShortcutsDialog(Gtk.ShortcutsWindow):
__gtype_name__ = 'McgShortcutsDialog' __gtype_name__ = 'McgShortcutsDialog'
def __init__(self): def __init__(self):
super().__init__() super().__init__()

View file

@ -1,23 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0')
import hashlib import hashlib
import locale import locale
import os import os
import urllib
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, GdkPixbuf, GObject, Gtk from gi.repository import Gdk, GdkPixbuf, GObject, Gtk
class Utils: class Utils:
CSS_SELECTION = 'selection' CSS_SELECTION = 'selection'
STOCK_ICON_DEFAULT = 'image-x-generic-symbolic' STOCK_ICON_DEFAULT = 'image-x-generic-symbolic'
def load_pixbuf(data): def load_pixbuf(data):
loader = GdkPixbuf.PixbufLoader() loader = GdkPixbuf.PixbufLoader()
try: try:
@ -26,7 +21,6 @@ class Utils:
loader.close() loader.close()
return loader.get_pixbuf() return loader.get_pixbuf()
def load_thumbnail(cache, client, album, size): def load_thumbnail(cache, client, album, size):
cache_url = cache.create_filename(album) cache_url = cache.create_filename(album)
pixbuf = None pixbuf = None
@ -39,38 +33,31 @@ class Utils:
if albumart: if albumart:
pixbuf = Utils.load_pixbuf(albumart) pixbuf = Utils.load_pixbuf(albumart)
if pixbuf is not None: if pixbuf is not None:
pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER) pixbuf = pixbuf.scale_simple(size, size,
GdkPixbuf.InterpType.HYPER)
pixbuf.savev(cache_url, 'jpeg', [], []) pixbuf.savev(cache_url, 'jpeg', [], [])
return pixbuf return pixbuf
def create_artists_label(album): def create_artists_label(album):
label = ', '.join(album.get_albumartists()) label = ', '.join(album.get_albumartists())
if album.get_artists(): if album.get_artists():
label = locale.gettext("{} feat. {}").format( label = locale.gettext("{} feat. {}").format(
label, label, ", ".join(album.get_artists()))
", ".join(album.get_artists())
)
return label return label
def create_length_label(album): def create_length_label(album):
minutes = album.get_length() // 60 minutes = album.get_length() // 60
seconds = album.get_length() - minutes * 60 seconds = album.get_length() - minutes * 60
return locale.gettext("{}:{} minutes").format(minutes, seconds) return locale.gettext("{}:{} minutes").format(minutes, seconds)
def create_track_title(track): def create_track_title(track):
title = track.get_title() title = track.get_title()
if track.get_artists(): if track.get_artists():
title = locale.gettext("{} feat. {}").format( title = locale.gettext("{} feat. {}").format(
title, title, ", ".join(track.get_artists()))
", ".join(track.get_artists())
)
return title return title
def generate_id(values): def generate_id(values):
if type(values) is not list: if type(values) is not list:
values = [values] values = [values]
@ -80,8 +67,6 @@ class Utils:
return m.hexdigest() return m.hexdigest()
class SortOrder: class SortOrder:
ARTIST = 0 ARTIST = 0
TITLE = 1 TITLE = 1
@ -89,45 +74,35 @@ class SortOrder:
MODIFIED = 3 MODIFIED = 3
class GridItem(GObject.GObject): class GridItem(GObject.GObject):
__gtype_name__ = "GridItem" __gtype_name__ = "GridItem"
tooltip = GObject.Property(type=str, default=None) tooltip = GObject.Property(type=str, default=None)
cover = GObject.Property(type=Gdk.Paintable, default=None) cover = GObject.Property(type=Gdk.Paintable, default=None)
def __init__(self, album, cover): def __init__(self, album, cover):
super().__init__() super().__init__()
self._album = album self._album = album
if cover: if cover:
self.cover = Gdk.Texture.new_for_pixbuf(cover) self.cover = Gdk.Texture.new_for_pixbuf(cover)
self.tooltip = GObject.markup_escape_text("\n".join([ self.tooltip = GObject.markup_escape_text("\n".join([
album.get_title(), album.get_title(), ', '.join(album.get_dates()),
', '.join(album.get_dates()),
Utils.create_artists_label(album), Utils.create_artists_label(album),
Utils.create_length_label(album) Utils.create_length_label(album)
])) ]))
def get_album(self): def get_album(self):
return self._album return self._album
def set_cover(self, cover): def set_cover(self, cover):
self.cover = Gdk.Texture.new_for_pixbuf(cover) self.cover = Gdk.Texture.new_for_pixbuf(cover)
class SearchFilter(Gtk.Filter): class SearchFilter(Gtk.Filter):
def __init__(self, search_string): def __init__(self, search_string):
super().__init__() super().__init__()
self._search_string = search_string self._search_string = search_string
def do_match(self, grid_item): def do_match(self, grid_item):
return grid_item.get_album().filter(self._search_string) return grid_item.get_album().filter(self._search_string)

View file

@ -1,20 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
try: try:
import keyring import keyring
use_keyring = True use_keyring = True
except: except ImportError:
use_keyring = False use_keyring = False
use_keyring = False use_keyring = False
import locale import locale
import logging
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gdk, GObject, GLib, Gio from gi.repository import Gtk, Adw, Gdk, GObject, GLib, Gio
from . import client from . import client
from .shortcutsdialog import ShortcutsDialog from .shortcutsdialog import ShortcutsDialog
from .connectionpanel import ConnectionPanel from .connectionpanel import ConnectionPanel
@ -25,8 +22,6 @@ from .librarypanel import LibraryPanel
from .zeroconf import ZeroconfProvider from .zeroconf import ZeroconfProvider
class WindowState(GObject.Object): class WindowState(GObject.Object):
WIDTH = 'width' WIDTH = 'width'
HEIGHT = 'height' HEIGHT = 'height'
@ -37,13 +32,10 @@ class WindowState(GObject.Object):
is_maximized = GObject.Property(type=bool, default=False) is_maximized = GObject.Property(type=bool, default=False)
is_fullscreened = GObject.Property(type=bool, default=False) is_fullscreened = GObject.Property(type=bool, default=False)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/window.ui') @Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/window.ui')
class Window(Adw.ApplicationWindow): class Window(Adw.ApplicationWindow):
__gtype_name__ = 'McgAppWindow' __gtype_name__ = 'McgAppWindow'
@ -72,7 +64,6 @@ class Window(Adw.ApplicationWindow):
# Infobar # Infobar
info_toast = Gtk.Template.Child() info_toast = Gtk.Template.Child()
def __init__(self, app, title, settings, **kwargs): def __init__(self, app, title, settings, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.set_application(app) self.set_application(app)
@ -98,20 +89,35 @@ class Window(Adw.ApplicationWindow):
self._panels.append(self._cover_panel) self._panels.append(self._cover_panel)
# Playlist panel # Playlist panel
self._playlist_panel = PlaylistPanel(self._mcg) self._playlist_panel = PlaylistPanel(self._mcg)
self._playlist_panel.connect('open-standalone', self.on_panel_open_standalone) self._playlist_panel.connect('open-standalone',
self._playlist_panel.connect('close-standalone', self.on_panel_close_standalone) self.on_panel_open_standalone)
self._playlist_panel.connect('close-standalone',
self.on_panel_close_standalone)
self._panels.append(self._playlist_panel) self._panels.append(self._playlist_panel)
# Library panel # Library panel
self._library_panel = LibraryPanel(self._mcg) self._library_panel = LibraryPanel(self._mcg)
self._library_panel.connect('open-standalone', self.on_panel_open_standalone) self._library_panel.connect('open-standalone',
self._library_panel.connect('close-standalone', self.on_panel_close_standalone) self.on_panel_open_standalone)
self._library_panel.connect('close-standalone',
self.on_panel_close_standalone)
self._panels.append(self._library_panel) self._panels.append(self._library_panel)
# Stack # Stack
self.content_stack.add_child(self._connection_panel) self.content_stack.add_child(self._connection_panel)
self.panel_stack.add_titled_with_icon(self._server_panel, 'server-panel', locale.gettext("Server"), "network-wired-symbolic") self.panel_stack.add_titled_with_icon(self._server_panel,
self.panel_stack.add_titled_with_icon(self._cover_panel, 'cover-panel', locale.gettext("Cover"), "image-x-generic-symbolic") 'server-panel',
self.panel_stack.add_titled_with_icon(self._playlist_panel, 'playlist-panel', locale.gettext("Playlist"), "view-list-symbolic") locale.gettext("Server"),
self.panel_stack.add_titled_with_icon(self._library_panel, 'library-panel', locale.gettext("Library"), "emblem-music-symbolic") "network-wired-symbolic")
self.panel_stack.add_titled_with_icon(self._cover_panel, 'cover-panel',
locale.gettext("Cover"),
"image-x-generic-symbolic")
self.panel_stack.add_titled_with_icon(self._playlist_panel,
'playlist-panel',
locale.gettext("Playlist"),
"view-list-symbolic")
self.panel_stack.add_titled_with_icon(self._library_panel,
'library-panel',
locale.gettext("Library"),
"emblem-music-symbolic")
# Toolbar stack # Toolbar stack
self.toolbar_stack.add_child(self._server_panel.get_toolbar()) self.toolbar_stack.add_child(self._server_panel.get_toolbar())
self.toolbar_stack.add_child(self._cover_panel.get_toolbar()) self.toolbar_stack.add_child(self._cover_panel.get_toolbar())
@ -120,56 +126,92 @@ class Window(Adw.ApplicationWindow):
# Properties # Properties
self._set_headerbar_sensitive(False, False) self._set_headerbar_sensitive(False, False)
self._connection_panel.set_host(self._settings.get_string(Window.SETTING_HOST)) self._connection_panel.set_host(
self._connection_panel.set_port(self._settings.get_int(Window.SETTING_PORT)) self._settings.get_string(Window.SETTING_HOST))
self._connection_panel.set_port(
self._settings.get_int(Window.SETTING_PORT))
if use_keyring: if use_keyring:
self._connection_panel.set_password(keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME)) self._connection_panel.set_password(
self._playlist_panel.set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE)) keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM,
self._library_panel.set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE)) ZeroconfProvider.KEYRING_USERNAME))
self._library_panel.set_sort_order(self._settings.get_enum(Window.SETTING_SORT_ORDER)) self._playlist_panel.set_item_size(
self._library_panel.set_sort_type(self._settings.get_boolean(Window.SETTING_SORT_TYPE)) self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._library_panel.set_item_size(
self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._library_panel.set_sort_order(
self._settings.get_enum(Window.SETTING_SORT_ORDER))
self._library_panel.set_sort_type(
self._settings.get_boolean(Window.SETTING_SORT_TYPE))
# Signals # Signals
self.connect("notify::default-width", self.on_resize) self.connect("notify::default-width", self.on_resize)
self.connect("notify::default-height", self.on_resize) self.connect("notify::default-height", self.on_resize)
self.connect("notify::maximized", self.on_maximized) self.connect("notify::maximized", self.on_maximized)
self.connect("notify::fullscreened", self.on_fullscreened) self.connect("notify::fullscreened", self.on_fullscreened)
self._connection_panel.connect('connection-changed', self.on_connection_panel_connection_changed) self._connection_panel.connect(
self.panel_stack.connect('notify::visible-child', self.on_stack_switched) 'connection-changed', self.on_connection_panel_connection_changed)
self._server_panel.connect('change-output-device', self.on_server_panel_output_device_changed) self.panel_stack.connect('notify::visible-child',
self._cover_panel.connect('toggle-fullscreen', self.on_cover_panel_toggle_fullscreen) self.on_stack_switched)
self._server_panel.connect('change-output-device',
self.on_server_panel_output_device_changed)
self._cover_panel.connect('toggle-fullscreen',
self.on_cover_panel_toggle_fullscreen)
self._cover_panel.connect('set-song', self.on_cover_panel_set_song) self._cover_panel.connect('set-song', self.on_cover_panel_set_song)
self._cover_panel.connect('albumart', self.on_cover_panel_albumart) self._cover_panel.connect('albumart', self.on_cover_panel_albumart)
self._playlist_panel.connect('clear-playlist', self.on_playlist_panel_clear_playlist) self._playlist_panel.connect('clear-playlist',
self._playlist_panel.connect('remove-album', self.on_playlist_panel_remove) self.on_playlist_panel_clear_playlist)
self._playlist_panel.connect('remove-multiple-albums', self.on_playlist_panel_remove_multiple) self._playlist_panel.connect('remove-album',
self.on_playlist_panel_remove)
self._playlist_panel.connect('remove-multiple-albums',
self.on_playlist_panel_remove_multiple)
self._playlist_panel.connect('play', self.on_playlist_panel_play) self._playlist_panel.connect('play', self.on_playlist_panel_play)
self._playlist_panel.connect('albumart', self.on_playlist_panel_albumart) self._playlist_panel.connect('albumart',
self.on_playlist_panel_albumart)
self._library_panel.connect('update', self.on_library_panel_update) self._library_panel.connect('update', self.on_library_panel_update)
self._library_panel.connect('play', self.on_library_panel_play) self._library_panel.connect('play', self.on_library_panel_play)
self._library_panel.connect('queue', self.on_library_panel_queue) self._library_panel.connect('queue', self.on_library_panel_queue)
self._library_panel.connect('queue-multiple', self.on_library_panel_queue_multiple) self._library_panel.connect('queue-multiple',
self._library_panel.connect('item-size-changed', self.on_library_panel_item_size_changed) self.on_library_panel_queue_multiple)
self._library_panel.connect('sort-order-changed', self.on_library_panel_sort_order_changed) self._library_panel.connect('item-size-changed',
self._library_panel.connect('sort-type-changed', self.on_library_panel_sort_type_changed) self.on_library_panel_item_size_changed)
self._library_panel.connect('sort-order-changed',
self.on_library_panel_sort_order_changed)
self._library_panel.connect('sort-type-changed',
self.on_library_panel_sort_type_changed)
self._library_panel.connect('albumart', self.on_library_panel_albumart) self._library_panel.connect('albumart', self.on_library_panel_albumart)
self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION, self.on_mcg_connect) self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION,
self._mcg.connect_signal(client.Client.SIGNAL_STATUS, self.on_mcg_status) self.on_mcg_connect)
self._mcg.connect_signal(client.Client.SIGNAL_STATUS,
self.on_mcg_status)
self._mcg.connect_signal(client.Client.SIGNAL_STATS, self.on_mcg_stats) self._mcg.connect_signal(client.Client.SIGNAL_STATS, self.on_mcg_stats)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_OUTPUT_DEVICES, self.on_mcg_load_output_devices) self._mcg.connect_signal(client.Client.SIGNAL_LOAD_OUTPUT_DEVICES,
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist) self.on_mcg_load_output_devices)
self._mcg.connect_signal(client.Client.SIGNAL_PULSE_ALBUMS, self.on_mcg_pulse_albums) self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST,
self._mcg.connect_signal(client.Client.SIGNAL_INIT_ALBUMS, self.on_mcg_init_albums) self.on_mcg_load_playlist)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums) self._mcg.connect_signal(client.Client.SIGNAL_PULSE_ALBUMS,
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMART, self.on_mcg_load_albumart) self.on_mcg_pulse_albums)
self._mcg.connect_signal(client.Client.SIGNAL_INIT_ALBUMS,
self.on_mcg_init_albums)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS,
self.on_mcg_load_albums)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMART,
self.on_mcg_load_albumart)
self._mcg.connect_signal(client.Client.SIGNAL_ERROR, self.on_mcg_error) self._mcg.connect_signal(client.Client.SIGNAL_ERROR, self.on_mcg_error)
self._settings.connect('changed::'+Window.SETTING_PANEL, self.on_settings_panel_changed) self._settings.connect('changed::' + Window.SETTING_PANEL,
self._settings.connect('changed::'+Window.SETTING_ITEM_SIZE, self.on_settings_item_size_changed) self.on_settings_panel_changed)
self._settings.connect('changed::'+Window.SETTING_SORT_ORDER, self.on_settings_sort_order_changed) self._settings.connect('changed::' + Window.SETTING_ITEM_SIZE,
self._settings.connect('changed::'+Window.SETTING_SORT_TYPE, self.on_settings_sort_type_changed) self.on_settings_item_size_changed)
self._settings.bind(Window.SETTING_WINDOW_WIDTH, self._state, WindowState.WIDTH, Gio.SettingsBindFlags.DEFAULT) self._settings.connect('changed::' + Window.SETTING_SORT_ORDER,
self._settings.bind(Window.SETTING_WINDOW_HEIGHT, self._state, WindowState.HEIGHT, Gio.SettingsBindFlags.DEFAULT) self.on_settings_sort_order_changed)
self._settings.bind(Window.SETTING_WINDOW_MAXIMIZED, self._state, WindowState.IS_MAXIMIZED, Gio.SettingsBindFlags.DEFAULT) self._settings.connect('changed::' + Window.SETTING_SORT_TYPE,
self.on_settings_sort_type_changed)
self._settings.bind(Window.SETTING_WINDOW_WIDTH, self._state,
WindowState.WIDTH, Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_HEIGHT, self._state,
WindowState.HEIGHT, Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_MAXIMIZED, self._state,
WindowState.IS_MAXIMIZED,
Gio.SettingsBindFlags.DEFAULT)
# Actions # Actions
self.set_default_size(self._state.width, self._state.height) self.set_default_size(self._state.width, self._state.height)
@ -180,50 +222,55 @@ class Window(Adw.ApplicationWindow):
self._connect() self._connect()
# Menu actions # Menu actions
self._connect_action = Gio.SimpleAction.new_stateful("connect", None, GLib.Variant.new_boolean(False)) self._connect_action = Gio.SimpleAction.new_stateful(
"connect", None, GLib.Variant.new_boolean(False))
self._connect_action.connect('change-state', self.on_menu_connect) self._connect_action.connect('change-state', self.on_menu_connect)
self.add_action(self._connect_action) self.add_action(self._connect_action)
self._play_action = Gio.SimpleAction.new_stateful("play", None, GLib.Variant.new_boolean(False)) self._play_action = Gio.SimpleAction.new_stateful(
"play", None, GLib.Variant.new_boolean(False))
self._play_action.set_enabled(False) self._play_action.set_enabled(False)
self._play_action.connect('change-state', self.on_menu_play) self._play_action.connect('change-state', self.on_menu_play)
self.add_action(self._play_action) self.add_action(self._play_action)
self._clear_playlist_action = Gio.SimpleAction.new("clear-playlist", None) self._clear_playlist_action = Gio.SimpleAction.new(
"clear-playlist", None)
self._clear_playlist_action.set_enabled(False) self._clear_playlist_action.set_enabled(False)
self._clear_playlist_action.connect('activate', self.on_menu_clear_playlist) self._clear_playlist_action.connect('activate',
self.on_menu_clear_playlist)
self.add_action(self._clear_playlist_action) self.add_action(self._clear_playlist_action)
panel_variant = GLib.Variant.new_string("0") panel_variant = GLib.Variant.new_string("0")
self._panel_action = Gio.SimpleAction.new_stateful("panel", panel_variant.get_type(), panel_variant) self._panel_action = Gio.SimpleAction.new_stateful(
"panel", panel_variant.get_type(), panel_variant)
self._panel_action.set_enabled(False) self._panel_action.set_enabled(False)
self._panel_action.connect('change-state', self.on_menu_panel) self._panel_action.connect('change-state', self.on_menu_panel)
self.add_action(self._panel_action) self.add_action(self._panel_action)
self._toggle_fullscreen_action = Gio.SimpleAction.new("toggle-fullscreen", None) self._toggle_fullscreen_action = Gio.SimpleAction.new(
"toggle-fullscreen", None)
self._toggle_fullscreen_action.set_enabled(True) self._toggle_fullscreen_action.set_enabled(True)
self._toggle_fullscreen_action.connect('activate', self.on_menu_toggle_fullscreen) self._toggle_fullscreen_action.connect('activate',
self.on_menu_toggle_fullscreen)
self.add_action(self._toggle_fullscreen_action) self.add_action(self._toggle_fullscreen_action)
self._search_library_action = Gio.SimpleAction.new("search-library", None) self._search_library_action = Gio.SimpleAction.new(
"search-library", None)
self._search_library_action.set_enabled(True) self._search_library_action.set_enabled(True)
self._search_library_action.connect('activate', self.on_menu_search_library) self._search_library_action.connect('activate',
self.on_menu_search_library)
self.add_action(self._search_library_action) self.add_action(self._search_library_action)
# Menu callbacks # Menu callbacks
def on_menu_connect(self, action, value): def on_menu_connect(self, action, value):
self._connect() self._connect()
def on_menu_play(self, action, value): def on_menu_play(self, action, value):
self._mcg.playpause() self._mcg.playpause()
def on_menu_clear_playlist(self, action, value): def on_menu_clear_playlist(self, action, value):
self._mcg.clear_playlist() self._mcg.clear_playlist()
def on_menu_panel(self, action, value): def on_menu_panel(self, action, value):
action.set_state(value) action.set_state(value)
self.panel_stack.set_visible_child(self._panels[int(value.get_string())]) self.panel_stack.set_visible_child(self._panels[int(
value.get_string())])
def on_menu_toggle_fullscreen(self, action, value): def on_menu_toggle_fullscreen(self, action, value):
self.panel_stack.set_visible_child(self._cover_panel) self.panel_stack.set_visible_child(self._cover_panel)
@ -232,12 +279,10 @@ class Window(Adw.ApplicationWindow):
else: else:
self.unfullscreen() self.unfullscreen()
def on_menu_search_library(self, action, value): def on_menu_search_library(self, action, value):
self.panel_stack.set_visible_child(self.library_panel_page) self.panel_stack.set_visible_child(self.library_panel_page)
self._library_panel.show_search() self._library_panel.show_search()
# Window callbacks # Window callbacks
def on_resize(self, widget, event): def on_resize(self, widget, event):
@ -251,15 +296,12 @@ class Window(Adw.ApplicationWindow):
GObject.idle_add(self._playlist_panel.set_size, width, height) GObject.idle_add(self._playlist_panel.set_size, width, height)
GObject.idle_add(self._library_panel.set_size, width, height) GObject.idle_add(self._library_panel.set_size, width, height)
def on_maximized(self, widget, maximized): def on_maximized(self, widget, maximized):
self._state.set_property(WindowState.IS_MAXIMIZED, maximized is True) self._state.set_property(WindowState.IS_MAXIMIZED, maximized is True)
def on_fullscreened(self, widget, fullscreened): def on_fullscreened(self, widget, fullscreened):
self._fullscreen(self.is_fullscreen()) self._fullscreen(self.is_fullscreen())
# HeaderBar callbacks # HeaderBar callbacks
@Gtk.Template.Callback() @Gtk.Template.Callback()
@ -267,12 +309,10 @@ class Window(Adw.ApplicationWindow):
if self._headerbar_connection_button_active: if self._headerbar_connection_button_active:
self._connect() self._connect()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_headerbar_volume_changed(self, widget, value): def on_headerbar_volume_changed(self, widget, value):
if not self._setting_volume: if not self._setting_volume:
self._mcg.set_volume(int(value*100)) self._mcg.set_volume(int(value * 100))
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_headerbar_playpause_toggled(self, widget): def on_headerbar_playpause_toggled(self, widget):
@ -280,7 +320,6 @@ class Window(Adw.ApplicationWindow):
self._mcg.playpause() self._mcg.playpause()
self._mcg.get_status() self._mcg.get_status()
# Panel callbacks # Panel callbacks
def on_stack_switched(self, widget, prop): def on_stack_switched(self, widget, prop):
@ -290,100 +329,85 @@ class Window(Adw.ApplicationWindow):
for panel in self._panels: for panel in self._panels:
panel.set_selected(panel == self.panel_stack.get_visible_child()) panel.set_selected(panel == self.panel_stack.get_visible_child())
def on_panel_open_standalone(self, panel): def on_panel_open_standalone(self, panel):
self.toolbar_view.add_top_bar(panel.get_headerbar_standalone()) self.toolbar_view.add_top_bar(panel.get_headerbar_standalone())
self.toolbar_view.remove(self.headerbar) self.toolbar_view.remove(self.headerbar)
def on_panel_close_standalone(self, panel): def on_panel_close_standalone(self, panel):
self.toolbar_view.add_top_bar(self.headerbar) self.toolbar_view.add_top_bar(self.headerbar)
self.toolbar_view.remove(panel.get_headerbar_standalone()) self.toolbar_view.remove(panel.get_headerbar_standalone())
def on_connection_panel_connection_changed(self, widget, host, port,
def on_connection_panel_connection_changed(self, widget, host, port, password): password):
self._settings.set_string(Window.SETTING_HOST, host) self._settings.set_string(Window.SETTING_HOST, host)
self._settings.set_int(Window.SETTING_PORT, port) self._settings.set_int(Window.SETTING_PORT, port)
if use_keyring: if use_keyring:
if password: if password:
keyring.set_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME, password) keyring.set_password(ZeroconfProvider.KEYRING_SYSTEM,
ZeroconfProvider.KEYRING_USERNAME,
password)
else: else:
if keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME): if keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM,
keyring.delete_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME) ZeroconfProvider.KEYRING_USERNAME):
keyring.delete_password(ZeroconfProvider.KEYRING_SYSTEM,
ZeroconfProvider.KEYRING_USERNAME)
def on_playlist_panel_clear_playlist(self, widget): def on_playlist_panel_clear_playlist(self, widget):
self._mcg.clear_playlist() self._mcg.clear_playlist()
def on_playlist_panel_remove(self, widget, album): def on_playlist_panel_remove(self, widget, album):
self._mcg.remove_album_from_playlist(album) self._mcg.remove_album_from_playlist(album)
def on_playlist_panel_remove_multiple(self, widget, albums): def on_playlist_panel_remove_multiple(self, widget, albums):
self._mcg.remove_albums_from_playlist(albums) self._mcg.remove_albums_from_playlist(albums)
def on_playlist_panel_play(self, widget, album): def on_playlist_panel_play(self, widget, album):
self._mcg.play_album_from_playlist(album) self._mcg.play_album_from_playlist(album)
def on_playlist_panel_albumart(self, widget, album): def on_playlist_panel_albumart(self, widget, album):
self._mcg.get_albumart(album) self._mcg.get_albumart(album)
def on_server_panel_output_device_changed(self, widget, device, enabled): def on_server_panel_output_device_changed(self, widget, device, enabled):
self._mcg.enable_output_device(device, enabled) self._mcg.enable_output_device(device, enabled)
def on_cover_panel_toggle_fullscreen(self, widget): def on_cover_panel_toggle_fullscreen(self, widget):
if not self._state.get_property(WindowState.IS_FULLSCREENED): if not self._state.get_property(WindowState.IS_FULLSCREENED):
self.fullscreen() self.fullscreen()
else: else:
self.unfullscreen() self.unfullscreen()
def on_cover_panel_set_song(self, widget, pos, time): def on_cover_panel_set_song(self, widget, pos, time):
self._mcg.seek(pos, time) self._mcg.seek(pos, time)
def on_cover_panel_albumart(self, widget, album): def on_cover_panel_albumart(self, widget, album):
self._mcg.get_albumart(album) self._mcg.get_albumart(album)
def on_library_panel_update(self, widget): def on_library_panel_update(self, widget):
self._mcg.update() self._mcg.update()
def on_library_panel_play(self, widget, album): def on_library_panel_play(self, widget, album):
self._mcg.play_album(album) self._mcg.play_album(album)
def on_library_panel_queue(self, widget, album): def on_library_panel_queue(self, widget, album):
self._mcg.queue_album(album) self._mcg.queue_album(album)
def on_library_panel_queue_multiple(self, widget, albums): def on_library_panel_queue_multiple(self, widget, albums):
self._mcg.queue_albums(albums) self._mcg.queue_albums(albums)
def on_library_panel_item_size_changed(self, widget, size): def on_library_panel_item_size_changed(self, widget, size):
self._playlist_panel.set_item_size(size) self._playlist_panel.set_item_size(size)
self._settings.set_int(Window.SETTING_ITEM_SIZE, self._library_panel.get_item_size()) self._settings.set_int(Window.SETTING_ITEM_SIZE,
self._library_panel.get_item_size())
def on_library_panel_sort_order_changed(self, widget, sort_order): def on_library_panel_sort_order_changed(self, widget, sort_order):
self._settings.set_enum(Window.SETTING_SORT_ORDER, sort_order) self._settings.set_enum(Window.SETTING_SORT_ORDER, sort_order)
def on_library_panel_sort_type_changed(self, widget, sort_type): def on_library_panel_sort_type_changed(self, widget, sort_type):
self._settings.set_boolean(Window.SETTING_SORT_TYPE, sort_type) self._settings.set_boolean(Window.SETTING_SORT_TYPE, sort_type)
def on_library_panel_albumart(self, widget, album): def on_library_panel_albumart(self, widget, album):
self._mcg.get_albumart(album) self._mcg.get_albumart(album)
# MCG callbacks # MCG callbacks
def on_mcg_connect(self, connected): def on_mcg_connect(self, connected):
@ -405,8 +429,8 @@ class Window(Adw.ApplicationWindow):
self._clear_playlist_action.set_enabled(False) self._clear_playlist_action.set_enabled(False)
self._panel_action.set_enabled(False) self._panel_action.set_enabled(False)
def on_mcg_status(self, state, album, pos, time, volume, file, audio,
def on_mcg_status(self, state, album, pos, time, volume, file, audio, bitrate, error): bitrate, error):
# Album # Album
GObject.idle_add(self._cover_panel.set_album, album) GObject.idle_add(self._cover_panel.set_album, album)
if not album and self._state.get_property(WindowState.IS_FULLSCREENED): if not album and self._state.get_property(WindowState.IS_FULLSCREENED):
@ -428,64 +452,55 @@ class Window(Adw.ApplicationWindow):
if error: if error:
self._show_error(error) self._show_error(error)
def on_mcg_stats(self, artists, albums, songs, dbplaytime, playtime,
def on_mcg_stats(self, artists, albums, songs, dbplaytime, playtime, uptime): uptime):
self._server_panel.set_stats(artists, albums, songs, dbplaytime, playtime, uptime) self._server_panel.set_stats(artists, albums, songs, dbplaytime,
playtime, uptime)
def on_mcg_load_output_devices(self, devices): def on_mcg_load_output_devices(self, devices):
self._server_panel.set_output_devices(devices) self._server_panel.set_output_devices(devices)
def on_mcg_load_playlist(self, playlist): def on_mcg_load_playlist(self, playlist):
self._playlist_panel.set_playlist(self._connection_panel.get_host(), playlist) self._playlist_panel.set_playlist(self._connection_panel.get_host(),
playlist)
def on_mcg_init_albums(self): def on_mcg_init_albums(self):
GObject.idle_add(self._library_panel.init_albums) GObject.idle_add(self._library_panel.init_albums)
def on_mcg_pulse_albums(self): def on_mcg_pulse_albums(self):
GObject.idle_add(self._library_panel.load_albums) GObject.idle_add(self._library_panel.load_albums)
def on_mcg_load_albums(self, albums): def on_mcg_load_albums(self, albums):
self._library_panel.set_albums(self._connection_panel.get_host(), albums) self._library_panel.set_albums(self._connection_panel.get_host(),
albums)
def on_mcg_load_albumart(self, album, data): def on_mcg_load_albumart(self, album, data):
self._cover_panel.set_albumart(album, data) self._cover_panel.set_albumart(album, data)
self._playlist_panel.set_albumart(album, data) self._playlist_panel.set_albumart(album, data)
self._library_panel.set_albumart(album, data) self._library_panel.set_albumart(album, data)
def on_mcg_error(self, error): def on_mcg_error(self, error):
GObject.idle_add(self._show_error, str(error)) GObject.idle_add(self._show_error, str(error))
# Settings callbacks # Settings callbacks
def on_settings_panel_changed(self, settings, key): def on_settings_panel_changed(self, settings, key):
panel_index = settings.get_int(key) panel_index = settings.get_int(key)
self.panel_stack.set_visible_child(self._panels[panel_index]) self.panel_stack.set_visible_child(self._panels[panel_index])
def on_settings_item_size_changed(self, settings, key): def on_settings_item_size_changed(self, settings, key):
size = settings.get_int(key) size = settings.get_int(key)
self._playlist_panel.set_item_size(size) self._playlist_panel.set_item_size(size)
self._library_panel.set_item_size(size) self._library_panel.set_item_size(size)
def on_settings_sort_order_changed(self, settings, key): def on_settings_sort_order_changed(self, settings, key):
sort_order = settings.get_enum(key) sort_order = settings.get_enum(key)
self._library_panel.set_sort_order(sort_order) self._library_panel.set_sort_order(sort_order)
def on_settings_sort_type_changed(self, settings, key): def on_settings_sort_type_changed(self, settings, key):
sort_type = settings.get_boolean(key) sort_type = settings.get_boolean(key)
self._library_panel.set_sort_type(sort_type) self._library_panel.set_sort_type(sort_type)
# Private methods # Private methods
def _connect(self): def _connect(self):
@ -501,27 +516,27 @@ class Window(Adw.ApplicationWindow):
self._mcg.connect(host, port, password) self._mcg.connect(host, port, password)
self._settings.set_boolean(Window.SETTING_CONNECTED, True) self._settings.set_boolean(Window.SETTING_CONNECTED, True)
def _connect_connected(self): def _connect_connected(self):
self._headerbar_connected() self._headerbar_connected()
self._set_headerbar_sensitive(True, False) self._set_headerbar_sensitive(True, False)
self.content_stack.set_visible_child(self.panel_stack) self.content_stack.set_visible_child(self.panel_stack)
self.panel_stack.set_visible_child(self._panels[self._settings.get_int(Window.SETTING_PANEL)]) self.panel_stack.set_visible_child(self._panels[self._settings.get_int(
Window.SETTING_PANEL)])
def _connect_disconnected(self): def _connect_disconnected(self):
self._playlist_panel.stop_threads(); self._playlist_panel.stop_threads()
self._library_panel.stop_threads(); self._library_panel.stop_threads()
self._headerbar_disconnected() self._headerbar_disconnected()
self._set_headerbar_sensitive(False, False) self._set_headerbar_sensitive(False, False)
self._save_visible_panel() self._save_visible_panel()
self.content_stack.set_visible_child(self._connection_panel) self.content_stack.set_visible_child(self._connection_panel)
self._connection_panel.set_sensitive(True) self._connection_panel.set_sensitive(True)
def _fullscreen(self, fullscreened_new): def _fullscreen(self, fullscreened_new):
if fullscreened_new != self._state.get_property(WindowState.IS_FULLSCREENED): if fullscreened_new != self._state.get_property(
self._state.set_property(WindowState.IS_FULLSCREENED, fullscreened_new) WindowState.IS_FULLSCREENED):
self._state.set_property(WindowState.IS_FULLSCREENED,
fullscreened_new)
if self._state.get_property(WindowState.IS_FULLSCREENED): if self._state.get_property(WindowState.IS_FULLSCREENED):
self.headerbar.hide() self.headerbar.hide()
self._cover_panel.set_fullscreen(True) self._cover_panel.set_fullscreen(True)
@ -531,35 +546,33 @@ class Window(Adw.ApplicationWindow):
self._cover_panel.set_fullscreen(False) self._cover_panel.set_fullscreen(False)
self.set_cursor(Gdk.Cursor.new_from_name("default", None)) self.set_cursor(Gdk.Cursor.new_from_name("default", None))
def _save_visible_panel(self): def _save_visible_panel(self):
panel_index_selected = self._panels.index(self.panel_stack.get_visible_child()) panel_index_selected = self._panels.index(
self.panel_stack.get_visible_child())
self._settings.set_int(Window.SETTING_PANEL, panel_index_selected) self._settings.set_int(Window.SETTING_PANEL, panel_index_selected)
def _set_menu_visible_panel(self): def _set_menu_visible_panel(self):
panel_index_selected = self._panels.index(self.panel_stack.get_visible_child()) panel_index_selected = self._panels.index(
self._panel_action.set_state(GLib.Variant.new_string(str(panel_index_selected))) self.panel_stack.get_visible_child())
self._panel_action.set_state(
GLib.Variant.new_string(str(panel_index_selected)))
def _set_visible_toolbar(self): def _set_visible_toolbar(self):
panel_index_selected = self._panels.index(self.panel_stack.get_visible_child()) panel_index_selected = self._panels.index(
self.panel_stack.get_visible_child())
toolbar = self._panels[panel_index_selected].get_toolbar() toolbar = self._panels[panel_index_selected].get_toolbar()
self.toolbar_stack.set_visible_child(toolbar) self.toolbar_stack.set_visible_child(toolbar)
def _set_play(self): def _set_play(self):
self._headerbar_playpause_button_active = False self._headerbar_playpause_button_active = False
self.headerbar_button_playpause.set_active(True) self.headerbar_button_playpause.set_active(True)
self._headerbar_playpause_button_active = True self._headerbar_playpause_button_active = True
def _set_pause(self): def _set_pause(self):
self._headerbar_playpause_button_active = False self._headerbar_playpause_button_active = False
self.headerbar_button_playpause.set_active(False) self.headerbar_button_playpause.set_active(False)
self._headerbar_playpause_button_active = True self._headerbar_playpause_button_active = True
def _set_volume(self, volume): def _set_volume(self, volume):
if volume >= 0: if volume >= 0:
self.headerbar_button_volume.set_visible(True) self.headerbar_button_volume.set_visible(True)
@ -569,27 +582,23 @@ class Window(Adw.ApplicationWindow):
else: else:
self.headerbar_button_volume.set_visible(False) self.headerbar_button_volume.set_visible(False)
def _headerbar_connected(self): def _headerbar_connected(self):
self._headerbar_connection_button_active = False self._headerbar_connection_button_active = False
self.headerbar_button_connect.set_active(True) self.headerbar_button_connect.set_active(True)
self.headerbar_button_connect.set_state(True) self.headerbar_button_connect.set_state(True)
self._headerbar_connection_button_active = True self._headerbar_connection_button_active = True
def _headerbar_disconnected(self): def _headerbar_disconnected(self):
self._headerbar_connection_button_active = False self._headerbar_connection_button_active = False
self.headerbar_button_connect.set_active(False) self.headerbar_button_connect.set_active(False)
self.headerbar_button_connect.set_state(False) self.headerbar_button_connect.set_state(False)
self._headerbar_connection_button_active = True self._headerbar_connection_button_active = True
def _set_headerbar_sensitive(self, sensitive, connecting): def _set_headerbar_sensitive(self, sensitive, connecting):
self.headerbar_button_playpause.set_sensitive(sensitive) self.headerbar_button_playpause.set_sensitive(sensitive)
self.headerbar_button_volume.set_sensitive(sensitive) self.headerbar_button_volume.set_sensitive(sensitive)
self.headerbar_panel_switcher.set_sensitive(sensitive) self.headerbar_panel_switcher.set_sensitive(sensitive)
self.headerbar_button_connect.set_sensitive(not connecting) self.headerbar_button_connect.set_sensitive(not connecting)
def _show_error(self, message): def _show_error(self, message):
self.info_toast.add_toast(Adw.Toast.new(message)) self.info_toast.add_toast(Adw.Toast.new(message))

View file

@ -1,27 +1,23 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi import gi
try: try:
gi.require_version('Avahi', '0.6') gi.require_version('Avahi', '0.6')
from gi.repository import Avahi from gi.repository import Avahi
use_avahi = True use_avahi = True
except: except ValueError | ImportError:
use_avahi = False use_avahi = False
import logging import logging
from mcg import client from mcg import client
class ZeroconfProvider(client.Base): class ZeroconfProvider(client.Base):
KEYRING_SYSTEM = 'mcg' KEYRING_SYSTEM = 'mcg'
KEYRING_USERNAME = 'mpd' KEYRING_USERNAME = 'mpd'
SIGNAL_SERVICE_NEW = 'service-new' SIGNAL_SERVICE_NEW = 'service-new'
TYPE = '_mpd._tcp' TYPE = '_mpd._tcp'
def __init__(self): def __init__(self):
client.Base.__init__(self) client.Base.__init__(self)
self._service_resolvers = [] self._service_resolvers = []
@ -31,35 +27,46 @@ class ZeroconfProvider(client.Base):
if use_avahi: if use_avahi:
self._start_client() self._start_client()
def on_new_service(self, browser, interface, protocol, name, type, domain,
def on_new_service(self, browser, interface, protocol, name, type, domain, flags): flags):
#if not (flags & Avahi.LookupResultFlags.GA_LOOKUP_RESULT_LOCAL): #if not (flags & Avahi.LookupResultFlags.GA_LOOKUP_RESULT_LOCAL):
service_resolver = Avahi.ServiceResolver(interface=interface, protocol=protocol, name=name, type=type, domain=domain, aprotocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC, flags=0,) service_resolver = Avahi.ServiceResolver(
interface=interface,
protocol=protocol,
name=name,
type=type,
domain=domain,
aprotocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC,
flags=0,
)
service_resolver.connect('found', self.on_found) service_resolver.connect('found', self.on_found)
service_resolver.connect('failure', self.on_failure) service_resolver.connect('failure', self.on_failure)
service_resolver.attach(self._client) service_resolver.attach(self._client)
self._service_resolvers.append(service_resolver) self._service_resolvers.append(service_resolver)
def on_found(self, resolver, interface, protocol, name, type, domain, host,
def on_found(self, resolver, interface, protocol, name, type, domain, host, date, port, *args): date, port, *args):
if (host, port) not in self._services.keys(): if (host, port) not in self._services.keys():
service = (name,host,port) service = (name, host, port)
self._services[(host,port)] = service self._services[(host, port)] = service
self._callback(ZeroconfProvider.SIGNAL_SERVICE_NEW, service) self._callback(ZeroconfProvider.SIGNAL_SERVICE_NEW, service)
def on_failure(self, resolver, date): def on_failure(self, resolver, date):
if resolver in self._service_resolvers: if resolver in self._service_resolvers:
self._service_resolvers.remove(resolver) self._service_resolvers.remove(resolver)
def _start_client(self): def _start_client(self):
self._logger.info("Starting Avahi client") self._logger.info("Starting Avahi client")
self._client = Avahi.Client(flags=0,) self._client = Avahi.Client(flags=0, )
try: try:
self._client.start() self._client.start()
# Browser # Browser
self._service_browser = Avahi.ServiceBrowser(domain='local', flags=0, interface=-1, protocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC, type=ZeroconfProvider.TYPE) self._service_browser = Avahi.ServiceBrowser(
domain='local',
flags=0,
interface=-1,
protocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC,
type=ZeroconfProvider.TYPE)
self._service_browser.connect('new_service', self.on_new_service) self._service_browser.connect('new_service', self.on_new_service)
self._service_browser.attach(self._client) self._service_browser.attach(self._client)
except Exception as e: except Exception as e: