Skip to content

Commit

Permalink
Modernise entire preferences UI 🎨 (#4175)
Browse files Browse the repository at this point in the history
* Modernise entire preferences UI 🎨

 * Convert to Gtk.Switch everywhere. It's the one true way now.
 * Rework the player prefs too
 * Make prefs window a bit more resize-friendly
 * Simplify some UI elements
 * Use FlowBox for column headings
 * Add a few icons for consistency
 * Fix a few tooltips etc
 * Add a behavio(u)r frame for Player
 * Nicer margins

* Prefs: make more things scales

 * Also make RG integer values
 * And fallback -12dB -> +6dB, higher than this is madness anyway
 * Format buffer slider as `s` not `seconds`

* Prefs: simplify code by reuse

* Neater debug pipeline button

* Prefs: make more things scales

 * Also make RG integer values
 * And fallback -12dB -> +6dB, higher than this is madness anyway
 * Format buffer slider as `s` not `seconds`
  • Loading branch information
declension committed Oct 22, 2022
1 parent 6b92603 commit e24d6e3
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 291 deletions.
82 changes: 43 additions & 39 deletions quodlibet/player/gstbe/prefs.py
@@ -1,6 +1,6 @@
# Copyright 2004-2011 Joe Wreschnig, Michael Urman, Steven Robertson,
# 2011-2014 Christoph Reiter
# 2020 Nick Boultbee
# 2020-2022 Nick Boultbee
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -11,7 +11,7 @@

from quodlibet import config
from quodlibet import _
from quodlibet.qltk.ccb import ConfigCheckButton
from quodlibet.qltk.ccb import ConfigCheckButton, ConfigSwitch
from quodlibet.qltk.entry import UndoEntry
from quodlibet.qltk.x import Button
from quodlibet.qltk import Icons
Expand All @@ -20,12 +20,12 @@

class GstPlayerPreferences(Gtk.VBox):
def __init__(self, player, debug=False):
super().__init__(spacing=6)
super().__init__(spacing=12)

e = UndoEntry()
e.set_tooltip_text(_("The GStreamer output pipeline used for "
"playback. Leave blank for the default pipeline. "
"In case the pipeline contains a sink, "
"If the pipeline contains a sink, "
"it will be used instead of the default one."))

e.set_text(config.get('player', 'gst_pipeline'))
Expand All @@ -39,19 +39,19 @@ def changed(entry):
pipe_label.set_use_underline(True)
pipe_label.set_mnemonic_widget(e)

apply_button = Button(_("_Apply"))
apply_button = Button(_("_Apply"), Icons.VIEW_REFRESH)

def format_buffer(scale, value):
return _("%.1f seconds") % value
# Translators: s = seconds
return _("%.1f s") % value

def scale_changed(scale):
duration_msec = int(scale.get_value() * 1000)
player._set_buffer_duration(duration_msec)

duration = config.getfloat("player", "gst_buffer")
scale = Gtk.HScale.new(
Gtk.Adjustment(value=duration, lower=0.2, upper=10))
scale.set_value_pos(Gtk.PositionType.RIGHT)
scale = Gtk.HScale.new(Gtk.Adjustment(value=duration, lower=0.2, upper=10))
scale.set_value_pos(Gtk.PositionType.LEFT)
scale.set_show_fill_level(True)
scale.connect('format-value', format_buffer)
scale.connect('value-changed', scale_changed)
Expand All @@ -65,53 +65,57 @@ def rebuild_pipeline(*args):

apply_button.connect('clicked', rebuild_pipeline)

gapless_button = ConfigCheckButton(
gapless_button = ConfigSwitch(
_('Disable _gapless playback'),
"player", "gst_disable_gapless", populate=True,
tooltip=_("Disabling gapless playback can avoid track changing problems "
"with some GStreamer versions"))

jack_button = ConfigCheckButton(
jack_button = ConfigSwitch(
_('Use JACK for playback if available'),
"player", "gst_use_jack", populate=True,
tooltip=_("Uses `jackaudiosink` for playbin sink if it can be detected"))
jack_connect = ConfigCheckButton(
jack_connect = ConfigSwitch(
_('Auto-connect to JACK output devices'),
"player", "gst_jack_auto_connect",
populate=True, tooltip=_("Tells `jackaudiosink` to auto-connect"))

def _jack_toggled(widget: ConfigCheckButton) -> None:
def _jack_activated(widget: ConfigCheckButton, *args) -> None:
jack_connect.set_sensitive(widget.get_active())

jack_button.connect("clicked", _jack_toggled)
_jack_toggled(jack_button)
widgets = [(pipe_label, e, apply_button),
(buffer_label, scale, None)]

table = Gtk.Table(n_rows=len(widgets) + 3, n_columns=3)
table.set_col_spacings(6)
table.set_row_spacings(6)
for i, (left, middle, right) in enumerate(widgets):
left.set_alignment(0.0, 0.5)
table.attach(left, 0, 1, i, i + 1,
xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK)
if right:
table.attach(middle, 1, 2, i, i + 1)
table.attach(right, 2, 3, i, i + 1,
xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK)
else:
table.attach(middle, 1, 3, i, i + 1)

table.attach(gapless_button, 0, 3, 2, 3)
table.attach(jack_button, 0, 3, 3, 4)
table.attach(jack_connect, 0, 3, 4, 5)

self.pack_start(table, True, True, 0)
jack_button.connect("notify::active", _jack_activated)
_jack_activated(jack_button, None)

hb = self._create_pipeline_box(pipe_label, e, apply_button)
self.pack_start(hb, False, False, 0)

# Buffer
hb = self._create_buffer_box(buffer_label, scale)
self.pack_start(hb, False, False, 0)

self.pack_start(gapless_button, False, False, 0)
self.pack_start(jack_button, False, False, 0)
self.pack_start(jack_connect, False, False, 0)

if debug:
def print_bin(player):
player._print_pipeline()

b = Button("Print Pipeline", Icons.DIALOG_INFORMATION)
connect_obj(b, 'clicked', print_bin, player)
self.pack_start(b, True, True, 0)
hb = Gtk.Box(spacing=6)
hb.pack_end(b, False, False, 0)
self.pack_start(hb, False, False, 0)

def _create_buffer_box(self, label: Gtk.Label, scale: Gtk.HScale):
hb = Gtk.Box(spacing=6)
hb.pack_start(label, False, False, 0)
hb.pack_end(scale, True, True, 0)
return hb

def _create_pipeline_box(self, pipe_label: Gtk.Label, e: Gtk.Widget,
apply_button: Gtk.Button):
hb = Gtk.Box(spacing=12)
hb.pack_start(pipe_label, False, False, 0)
hb.pack_start(e, True, True, 0)
hb.pack_end(apply_button, False, False, 0)
return hb
5 changes: 3 additions & 2 deletions quodlibet/qltk/__init__.py
@@ -1,6 +1,6 @@
# Copyright 2005 Joe Wreschnig, Michael Urman
# 2012 Christoph Reiter
# 2016-17 Nick Boultbee
# 2016-22 Nick Boultbee
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -10,6 +10,7 @@
import os
import signal
import socket
from typing import Union
from urllib.parse import urlparse

import gi
Expand Down Expand Up @@ -351,7 +352,7 @@ def is_accel(event, *accels):
return False


def add_css(widget, css):
def add_css(widget: Gtk.Widget, css: Union[bytes, str]):
"""Add css for the widget, overriding the theme.
Can raise GLib.GError in case the css is invalid
Expand Down
38 changes: 37 additions & 1 deletion quodlibet/qltk/ccb.py
@@ -1,5 +1,5 @@
# Copyright 2005 Joe Wreschnig, Michael Urman
# 2012 Nick Boultbee
# 2012-22 Nick Boultbee
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -34,6 +34,42 @@ def __toggled(self, section, option):
config.set(section, option, str(bool(self.get_active())).lower())


class ConfigSwitch(Gtk.Box):
"""A Switch that connects to QL's config module, and toggles
a boolean configuration value when it is toggled.
It is initialised to the current config value if `populate` is set True."""

def __init__(self, label, section, option, populate=False, tooltip=None,
default=None):
super().__init__()
self.label = Gtk.Label(label, use_underline=True)
self.switch = Gtk.Switch()
self.label.set_mnemonic_widget(self.switch)
self.pack_start(self.label, False, True, 0)
self.pack_end(self.switch, False, True, 0)
if default is None:
default = config._config.defaults.getboolean(section, option, True)

if populate:
self.set_active(config.getboolean(section, option, default))
if tooltip:
self.switch.set_tooltip_text(tooltip)
self.switch.connect('notify::active', self.__activated, section, option)

def set_active(self, value: bool):
self.switch.set_active(value)

def get_active(self) -> bool:
return self.switch.get_active()

def connect(self, *args, **kwargs):
self.switch.connect(*args, **kwargs)

def __activated(self, switch, state, section, option):
config.set(section, option, str(switch.get_active()).lower())


class ConfigCheckMenuItem(Gtk.CheckMenuItem):
"""A CheckMenuItem that connects to QL's config module, and toggles
a boolean configuration value when it is toggled.
Expand Down

0 comments on commit e24d6e3

Please sign in to comment.