Compare commits

..
11 changed files with 116 additions and 99 deletions

View file

@ -17,3 +17,16 @@ if os.path.exists(localedirdev):
# Set GSettings schema dir (if not set already)
if not os.environ.get('GSETTINGS_SCHEMA_DIR'):
os.environ['GSETTINGS_SCHEMA_DIR'] = datadirdev
class Environment:
"""Wrapper class to access environment settings."""
def get_srcdir():
return srcdir
def get_data(subdir):
return os.path.join(datadir, subdir)
def get_locale():
return localedir

View file

@ -14,12 +14,11 @@ class Application(Gtk.Application):
ID = 'xyz.suruatoel.mcg'
DOMAIN = 'mcg'
def __init__(self, version):
def __init__(self):
super().__init__(application_id=Application.ID,
flags=Gio.ApplicationFlags.FLAGS_NONE)
self._window = None
self._info_dialog = None
self._version = version
self._verbosity = logging.WARNING
self.set_accels_for_action('window.close', ['<primary>q'])
self.set_accels_for_action('win.show-help-overlay', ['<primary>k'])
@ -53,7 +52,7 @@ class Application(Gtk.Application):
self._info_dialog = Adw.AboutDialog()
self._info_dialog.set_application_icon("xyz.suruatoel.mcg")
self._info_dialog.set_application_name("CoverGrid")
self._info_dialog.set_version(self._version)
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.""")
@ -78,10 +77,10 @@ albums instead of single tracks.""")
style_manager.set_color_scheme(Adw.ColorScheme.PREFER_DARK)
def _load_css(self):
style_provider = Gtk.CssProvider()
style_provider.load_from_resource(self._get_resource_path('gtk.css'))
styleProvider = Gtk.CssProvider()
styleProvider.load_from_resource(self._get_resource_path('gtk.css'))
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(), style_provider,
Gdk.Display.get_default(), styleProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def _setup_actions(self):

View file

@ -22,7 +22,7 @@ class MPDException(Exception):
def _parse_error(self, error):
if error:
parts = re.match(r"\[(\d+)@(\d+)\]\s\{(\w+)\}\s(.*)", error)
parts = re.match("\[(\d+)@(\d+)\]\s\{(\w+)\}\s(.*)", error)
if parts:
self._error_number = int(parts.group(1))
self._command_number = int(parts.group(2))
@ -298,7 +298,7 @@ class Client(Base):
resources = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM, socket.IPPROTO_TCP)
for res in resources:
af, socktype, proto, _, sa = res
af, socktype, proto, canonname, sa = res
try:
sock = socket.socket(af, socktype, proto)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
@ -542,6 +542,8 @@ class Client(Base):
def _playpause(self):
"""Action: Perform the real play/pause command."""
#status = self._parse_dict(self._call('status'))
#if 'state' in status:
if self._state == 'play':
self._call('pause')
else:
@ -651,7 +653,7 @@ class Client(Base):
return future
def _add_action_future(self, future, method, *args):
"""Add an action to the action list based on a future."""
"""Add an action to the action list based on a futre."""
self._logger.debug("add action future %r (%r)", method.__name__, args)
action = (future, method, args)
self._actions.put(action)
@ -752,6 +754,7 @@ class Client(Base):
data = None
size = 1
offset = 0
index = 0
# Read data until size is reached
while offset < size:
@ -772,7 +775,7 @@ class Client(Base):
self._logger.debug("size: %d", size)
# For some commands the second line is the mimetype
if has_mimetype:
self._parse_dict([self._read_line()])['type']
mimetype = self._parse_dict([self._read_line()])['type']
# Next line is the count of bytes read
binary = int(self._parse_dict([self._read_line()])['binary'])
self._logger.debug("binary: %d", binary)
@ -785,7 +788,7 @@ class Client(Base):
# Read actual bytes
self._read_bytes(data_view, binary)
offset += binary
# Read line break to complete previous response
# Read line break to complete previous repsonse
self._read_line()
# Read command completion
end = self._read_line()
@ -828,12 +831,12 @@ class Client(Base):
self._buffer = buf
def _parse_dict(self, response):
dictionary = {}
dict = {}
if response:
for line in response:
key, value = self._split_line(line)
dictionary[key] = value
return dictionary
dict[key] = value
return dict
def _parse_list(self, response, delimiters):
entry = {}
@ -843,6 +846,11 @@ class Client(Base):
if entry and key in delimiters:
yield entry
entry = {}
#if key in entry.keys():
# if entry[key] is not list:
# entry[key] = [entry[key]]
# entry[key].append(value)
#else:
entry[key] = value
if entry:
yield entry
@ -855,13 +863,13 @@ class Client(Base):
album = None
if 'album' not in song:
song['album'] = MCGAlbum.DEFAULT_ALBUM
album_id = Utils.generate_id(song['album'])
if lookup and album_id in self._albums.keys():
album = self._albums[album_id]
id = Utils.generate_id(song['album'])
if lookup and id in self._albums.keys():
album = self._albums[id]
else:
album = MCGAlbum(song['album'], self._host)
if lookup:
self._albums[album_id] = album
self._albums[id] = album
return album
def _extract_track(self, song):
@ -919,7 +927,7 @@ class MCGAlbum:
def __init__(self, title, host):
self._artists = []
self._albumartists = []
self._paths = []
self._pathes = []
if type(title) is list:
title = title[0]
self._title = title
@ -981,8 +989,8 @@ class MCGAlbum:
):
self._dates.append(track.get_date())
path = os.path.dirname(track.get_file())
if path not in self._paths:
self._paths.append(path)
if path not in self._pathes:
self._pathes.append(path)
if track.get_last_modified():
if (
not self._last_modified
@ -1027,9 +1035,8 @@ class MCGAlbum:
return False
return True
@staticmethod
def compare(album1, album2, criterion=None, reverse=False):
if criterion is None:
if criterion == None:
criterion = SortOrder.TITLE
if criterion == SortOrder.ARTIST:
value_function = "get_artists"
@ -1040,22 +1047,22 @@ class MCGAlbum:
elif criterion == SortOrder.MODIFIED:
value_function = "get_last_modified"
reverse_multiplier = -1 if reverse else 1
reverseMultiplier = -1 if reverse else 1
value1 = getattr(album1, value_function)()
value2 = getattr(album2, value_function)()
if value1 is None and value2 is None:
return 0
elif value1 is None:
return -1 * reverse_multiplier
return -1 * reverseMultiplier
elif value2 is None:
return 1 * reverse_multiplier
return 1 * reverseMultiplier
if value1 < value2:
return -1 * reverse_multiplier
return -1 * reverseMultiplier
elif value1 == value2:
return 0
else:
return 1 * reverse_multiplier
return 1 * reverseMultiplier
class MCGTrack:
@ -1140,7 +1147,7 @@ class MCGTrack:
if date_string:
try:
self._last_modified = dateutil.parser.isoparse(date_string)
except ValueError:
except ValueError as e:
self._logger.debug("Invalid date format: %s", date_string)
def get_last_modified(self):

View file

@ -54,22 +54,21 @@ class CoverPanel(Gtk.Overlay):
GObject.idle_add(self._enable_tracklist)
# Click handler for image
click_controller = Gtk.GestureClick()
click_controller.connect('pressed', self.on_cover_box_pressed)
self.cover_box.add_controller(click_controller)
clickController = Gtk.GestureClick()
clickController.connect('pressed', self.on_cover_box_pressed)
self.cover_box.add_controller(clickController)
# Button controller for songs scale
button_controller = Gtk.GestureClick()
button_controller.connect('pressed', self.on_songs_scale_pressed)
button_controller.connect('unpaired-release',
self.on_songs_scale_released)
self.songs_scale.add_controller(button_controller)
buttonController = Gtk.GestureClick()
buttonController.connect('pressed', self.on_songs_scale_pressed)
buttonController.connect('unpaired-release',
self.on_songs_scale_released)
self.songs_scale.add_controller(buttonController)
def get_toolbar(self):
return self.toolbar
def set_selected(self, selected):
"""The cover panel does not use selections"""
pass
def on_cover_box_pressed(self, widget, npress, x, y):
@ -153,7 +152,7 @@ class CoverPanel(Gtk.Overlay):
# Load image and draw it
try:
self._cover_pixbuf = Utils.load_pixbuf(data)
except Exception:
except Exception as e:
self._logger.exception("Failed to set albumart")
self._cover_pixbuf = None
else:
@ -204,6 +203,9 @@ class CoverPanel(Gtk.Overlay):
self.cover_spinner.stop()
def _resize_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
# Get size
size_width = self.cover_stack.get_size(Gtk.Orientation.HORIZONTAL)
size_height = self.cover_stack.get_size(Gtk.Orientation.HORIZONTAL)
@ -224,10 +226,10 @@ class CoverPanel(Gtk.Overlay):
return
# Skalierungswert für Breite und Höhe ermitteln
ratio_w = float(size_width) / float(pixbuf.get_width())
ratio_h = float(size_height) / float(pixbuf.get_height())
ratioW = float(size_width) / float(pixbuf.get_width())
ratioH = float(size_height) / float(pixbuf.get_height())
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
ratio = min(ratio_w, ratio_h)
ratio = min(ratioW, ratioH)
ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width() * ratio))

View file

@ -116,10 +116,10 @@ class LibraryPanel(Adw.Bin):
}
# Button controller for grid scale
button_controller = Gtk.GestureClick()
button_controller.connect('unpaired-release',
self.on_grid_scale_released)
self.grid_scale.add_controller(button_controller)
buttonController = Gtk.GestureClick()
buttonController.connect('unpaired-release',
self.on_grid_scale_released)
self.grid_scale.add_controller(buttonController)
def get_headerbar_standalone(self):
return self._headerbar_standalone
@ -151,8 +151,8 @@ class LibraryPanel(Adw.Bin):
def on_grid_scale_released(self, widget, x, y, npress, sequence):
size = math.floor(self.grid_scale.get_value())
grid_range = self.grid_scale.get_adjustment()
if size < grid_range.get_lower() or size > grid_range.get_upper():
range = self.grid_scale.get_adjustment()
if size < range.get_lower() or size > range.get_upper():
return
self._item_size = size
self.emit('item-size-changed', size)
@ -162,8 +162,8 @@ class LibraryPanel(Adw.Bin):
@Gtk.Template.Callback()
def on_grid_scale_changed(self, widget):
size = math.floor(self.grid_scale.get_value())
grid_range = widget.get_adjustment()
if size < grid_range.get_lower() or size > grid_range.get_upper():
range = widget.get_adjustment()
if size < range.get_lower() or size > range.get_upper():
return
self._set_widget_grid_size(self.library_grid, size, True)
@ -200,9 +200,9 @@ class LibraryPanel(Adw.Bin):
# Get selected album
item = self._library_grid_filter.get_item(position)
album = item.get_album()
album_id = album.get_id()
id = album.get_id()
self._selected_albums = [album]
self.emit('albumart', album_id)
self.emit('albumart', id)
# Show standalone album
if widget.get_model() == self._library_grid_selection_single:
@ -297,7 +297,7 @@ class LibraryPanel(Adw.Bin):
# Load image and draw it
try:
self._standalone_pixbuf = Utils.load_pixbuf(data)
except Exception:
except Exception as e:
self._logger.exception("Failed to set albumart")
self._standalone_pixbuf = self._get_default_image()
else:
@ -415,9 +415,9 @@ class LibraryPanel(Adw.Bin):
lower = int(self.grid_scale.get_adjustment().get_lower())
upper = int(self.grid_scale.get_adjustment().get_upper())
count_min = max(int(width / upper), 1)
count_max = max(int(width / lower), 1)
for index in range(count_min, count_max):
countMin = max(int(width / upper), 1)
countMax = max(int(width / lower), 1)
for index in range(countMin, countMax):
pixel = int(width / index)
pixel = pixel - (2 * int(pixel / 100))
self.grid_scale.add_mark(pixel, Gtk.PositionType.BOTTOM, None)
@ -431,6 +431,9 @@ class LibraryPanel(Adw.Bin):
self.emit('close-standalone')
def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
# Get size
size_width = self.standalone_stack.get_width()
size_height = self.standalone_stack.get_height()
@ -442,10 +445,10 @@ class LibraryPanel(Adw.Bin):
return
# Skalierungswert für Breite und Höhe ermitteln
ratio_w = float(size_width) / float(pixbuf.get_width())
ratio_h = float(size_height) / float(pixbuf.get_height())
ratioW = float(size_width) / float(pixbuf.get_width())
ratioH = float(size_height) / float(pixbuf.get_height())
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
ratio = min(ratio_w, ratio_h)
ratio = min(ratioW, ratioH)
ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width() * ratio))

View file

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

View file

@ -110,9 +110,9 @@ class PlaylistPanel(Adw.Bin):
# Get selected album
item = self._playlist_grid_model.get_item(position)
album = item.get_album()
album_id = album.get_id()
id = album.get_id()
self._selected_albums = [album]
self.emit('albumart', album_id)
self.emit('albumart', id)
# Show standalone album
if widget.get_model() == self._playlist_grid_selection_single:
@ -175,7 +175,7 @@ class PlaylistPanel(Adw.Bin):
# Load image and draw it
try:
self._standalone_pixbuf = Utils.load_pixbuf(data)
except Exception:
except Exception as e:
self._logger.exception("Failed to set albumart")
self._cover_pixbuf = self._get_default_image()
else:
@ -239,6 +239,9 @@ class PlaylistPanel(Adw.Bin):
self.emit('close-standalone')
def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
# Get size
size_width = self.standalone_stack.get_width()
size_height = self.standalone_stack.get_height()
@ -250,10 +253,10 @@ class PlaylistPanel(Adw.Bin):
return
# Skalierungswert für Breite und Höhe ermitteln
ratio_w = float(size_width) / float(pixbuf.get_width())
ratio_h = float(size_height) / float(pixbuf.get_height())
ratioW = float(size_width) / float(pixbuf.get_width())
ratioH = float(size_height) / float(pixbuf.get_height())
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
ratio = min(ratio_w, ratio_h)
ratio = min(ratioW, ratioH)
ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width() * ratio))

View file

@ -31,7 +31,7 @@ class ServerPanel(Adw.Bin):
stats_dbplaytime = Gtk.Template.Child()
stats_playtime = Gtk.Template.Child()
stats_uptime = Gtk.Template.Child()
# Audio output devices widgets
# Audio ouptut devices widgets
output_devices = Gtk.Template.Child()
def __init__(self, **kwargs):
@ -103,8 +103,8 @@ class ServerPanel(Adw.Bin):
button = Gtk.CheckButton.new_with_label(device.get_name())
if device.is_enabled():
button.set_active(True)
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_buttons[device.get_id()] = button
@ -113,3 +113,4 @@ class ServerPanel(Adw.Bin):
if id not in device_ids:
self.output_devices.remove(
self._output_buttons[id].get_parent())

View file

@ -13,7 +13,6 @@ class Utils:
CSS_SELECTION = 'selection'
STOCK_ICON_DEFAULT = 'image-x-generic-symbolic'
@staticmethod
def load_pixbuf(data):
loader = GdkPixbuf.PixbufLoader()
try:
@ -22,7 +21,6 @@ class Utils:
loader.close()
return loader.get_pixbuf()
@staticmethod
def load_thumbnail(cache, client, album, size):
cache_url = cache.create_filename(album)
pixbuf = None
@ -40,7 +38,6 @@ class Utils:
pixbuf.savev(cache_url, 'jpeg', [], [])
return pixbuf
@staticmethod
def create_artists_label(album):
label = ', '.join(album.get_albumartists())
if album.get_artists():
@ -48,14 +45,12 @@ class Utils:
label, ", ".join(album.get_artists()))
return label
@staticmethod
def create_length_label(album):
minutes = album.get_length() // 60
seconds = album.get_length() - minutes * 60
return locale.gettext("{}:{} minutes").format(minutes, seconds)
@staticmethod
def create_track_title(track):
title = track.get_title()
if track.get_artists():
@ -63,7 +58,6 @@ class Utils:
title, ", ".join(track.get_artists()))
return title
@staticmethod
def generate_id(values):
if type(values) is not list:
values = [values]

View file

@ -23,10 +23,10 @@ from .zeroconf import ZeroconfProvider
class WindowState(GObject.Object):
PROP_WIDTH = 'width'
PROP_HEIGHT = 'height'
PROP_MAXIMIZED = 'is_maximized'
PROP_FULLSCREENED = 'is_fullscreened'
WIDTH = 'width'
HEIGHT = 'height'
IS_MAXIMIZED = 'is_maximized'
IS_FULLSCREENED = 'is_fullscreened'
width = GObject.Property(type=int, default=800)
height = GObject.Property(type=int, default=600)
is_maximized = GObject.Property(type=bool, default=False)
@ -206,18 +206,16 @@ class Window(Adw.ApplicationWindow):
self._settings.connect('changed::' + Window.SETTING_SORT_TYPE,
self.on_settings_sort_type_changed)
self._settings.bind(Window.SETTING_WINDOW_WIDTH, self._state,
WindowState.PROP_WIDTH,
Gio.SettingsBindFlags.DEFAULT)
WindowState.WIDTH, Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_HEIGHT, self._state,
WindowState.PROP_HEIGHT,
Gio.SettingsBindFlags.DEFAULT)
WindowState.HEIGHT, Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_MAXIMIZED, self._state,
WindowState.PROP_MAXIMIZED,
WindowState.IS_MAXIMIZED,
Gio.SettingsBindFlags.DEFAULT)
# Actions
self.set_default_size(self._state.width, self._state.height)
if self._state.get_property(WindowState.PROP_MAXIMIZED):
if self._state.get_property(WindowState.IS_MAXIMIZED):
self.maximize()
self.content_stack.set_visible_child(self._connection_panel)
if self._settings.get_boolean(Window.SETTING_CONNECTED):
@ -276,7 +274,7 @@ class Window(Adw.ApplicationWindow):
def on_menu_toggle_fullscreen(self, action, value):
self.panel_stack.set_visible_child(self._cover_panel)
if not self._state.get_property(WindowState.PROP_FULLSCREENED):
if not self._state.get_property(WindowState.IS_FULLSCREENED):
self.fullscreen()
else:
self.unfullscreen()
@ -292,14 +290,14 @@ class Window(Adw.ApplicationWindow):
height = self.get_size(Gtk.Orientation.VERTICAL)
if width > 0:
self._cover_panel.set_width(width)
if not self._state.get_property(WindowState.PROP_MAXIMIZED):
self._state.set_property(WindowState.PROP_WIDTH, width)
self._state.set_property(WindowState.PROP_HEIGHT, height)
if not self._state.get_property(WindowState.IS_MAXIMIZED):
self._state.set_property(WindowState.WIDTH, width)
self._state.set_property(WindowState.HEIGHT, height)
GObject.idle_add(self._playlist_panel.set_size, width, height)
GObject.idle_add(self._library_panel.set_size, width, height)
def on_maximized(self, widget, maximized):
self._state.set_property(WindowState.PROP_MAXIMIZED, maximized is True)
self._state.set_property(WindowState.IS_MAXIMIZED, maximized is True)
def on_fullscreened(self, widget, fullscreened):
self._fullscreen(self.is_fullscreen())
@ -373,7 +371,7 @@ class Window(Adw.ApplicationWindow):
self._mcg.enable_output_device(device, enabled)
def on_cover_panel_toggle_fullscreen(self, widget):
if not self._state.get_property(WindowState.PROP_FULLSCREENED):
if not self._state.get_property(WindowState.IS_FULLSCREENED):
self.fullscreen()
else:
self.unfullscreen()
@ -435,10 +433,7 @@ class Window(Adw.ApplicationWindow):
bitrate, error):
# Album
GObject.idle_add(self._cover_panel.set_album, album)
if (
not album
and self._state.get_property(WindowState.PROP_FULLSCREENED)
):
if not album and self._state.get_property(WindowState.IS_FULLSCREENED):
self._fullscreen(False)
# State
if state == 'play':
@ -539,11 +534,10 @@ class Window(Adw.ApplicationWindow):
def _fullscreen(self, fullscreened_new):
if fullscreened_new != self._state.get_property(
WindowState.PROP_FULLSCREENED
):
self._state.set_property(WindowState.PROP_FULLSCREENED,
WindowState.IS_FULLSCREENED):
self._state.set_property(WindowState.IS_FULLSCREENED,
fullscreened_new)
if self._state.get_property(WindowState.PROP_FULLSCREENED):
if self._state.get_property(WindowState.IS_FULLSCREENED):
self.headerbar.hide()
self._cover_panel.set_fullscreen(True)
self.set_cursor(Gdk.Cursor.new_from_name("none", None))

View file

@ -29,6 +29,7 @@ class ZeroconfProvider(client.Base):
def on_new_service(self, browser, interface, protocol, name, type, domain,
flags):
#if not (flags & Avahi.LookupResultFlags.GA_LOOKUP_RESULT_LOCAL):
service_resolver = Avahi.ServiceResolver(
interface=interface,
protocol=protocol,