rind: Implement a custom spin button for automatic pause controls
We have to do this because the "input" signal attached to a real Gtk.SpinButton is broken, and likes to increment from -1 to 1, bypassing 0 all together. Of course, this is only a problem when I try to have "This Track" and "Next Track" text displayed instead of numbers, but I think everything looks nicer this way. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
a226305623
commit
e09a4ed6ae
74
emmental.ui
74
emmental.ui
|
@ -7,11 +7,6 @@
|
|||
<mime-type>inode/directory</mime-type>
|
||||
</mime-types>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="pause_adjustment">
|
||||
<property name="upper">99</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="progress_adjustment">
|
||||
<property name="upper">100</property>
|
||||
<property name="step_increment">5</property>
|
||||
|
@ -663,16 +658,73 @@ audio-volume-medium-symbolic</property>
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="pause_after">
|
||||
<property name="can_focus">True</property>
|
||||
<object class="GtkBox" id="pause_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="primary_icon_name">media-playback-pause</property>
|
||||
<property name="placeholder_text" translatable="yes">Keep Playing</property>
|
||||
<property name="adjustment">pause_adjustment</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="pause_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="text" translatable="yes">Paused</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="primary_icon_name">media-playback-pause</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="pause_down">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">list-remove-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="minus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="KP_Subtract" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="pause_up">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="plus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="KP_Add" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
|
|
55
rind/gst.py
55
rind/gst.py
|
@ -3,16 +3,22 @@ from . import gtk
|
|||
import curds
|
||||
import datetime
|
||||
import gi
|
||||
import re
|
||||
import sys
|
||||
gi.require_version("Gst", "1.0")
|
||||
from gi.repository import Gst, GLib, Gdk
|
||||
from gi.repository import Gst, GLib, Gdk, Gtk
|
||||
|
||||
Gst.init(sys.argv)
|
||||
Adjustment = gtk.Builder.get_object("progress_adjustment")
|
||||
Artist = gtk.Builder.get_object("artist")
|
||||
Duration = gtk.Builder.get_object("duration")
|
||||
NextButton = gtk.Builder.get_object("next_button")
|
||||
PauseAdj = gtk.Builder.get_object("pause_adjustment")
|
||||
PauseButton = gtk.Builder.get_object("pause_button")
|
||||
PauseDown = gtk.Builder.get_object("pause_down")
|
||||
PauseEntry = gtk.Builder.get_object("pause_entry")
|
||||
PauseIcon = Gtk.EntryIconPosition.PRIMARY
|
||||
PauseUp = gtk.Builder.get_object("pause_up")
|
||||
PlayButton = gtk.Builder.get_object("play_button")
|
||||
Position = gtk.Builder.get_object("position")
|
||||
PrevButton = gtk.Builder.get_object("previous_button")
|
||||
|
@ -23,9 +29,10 @@ VolumeAdj = gtk.Builder.get_object("volume_adjustment")
|
|||
|
||||
class EmmentalAudio:
|
||||
def __init__(self):
|
||||
self.playbin = Gst.ElementFactory.make("playbin")
|
||||
self.bus = self.playbin.get_bus()
|
||||
self.have_next = False
|
||||
self.playbin = Gst.ElementFactory.make("playbin")
|
||||
self.bus = self.playbin.get_bus()
|
||||
self.have_next = False
|
||||
self.pause_count = -1
|
||||
|
||||
self.bus.add_signal_watch()
|
||||
|
||||
|
@ -35,6 +42,9 @@ class EmmentalAudio:
|
|||
self.prev_cb = PrevButton.connect( "clicked", self.previous)
|
||||
self.next_cb = NextButton.connect( "clicked", self.next)
|
||||
self.seek_cb = ProgScale.connect("change-value", self.seek)
|
||||
self.up_cb = PauseUp.connect( "clicked", self.pause_inc)
|
||||
self.down_cb = PauseDown.connect("clicked", self.pause_dec)
|
||||
self.input_cb = PauseEntry.connect("activate", self.pause_input)
|
||||
self.vol_cb = Volume.connect("value-changed", self.set_volume)
|
||||
|
||||
self.finish_cb = self.playbin.connect("about-to-finish",
|
||||
|
@ -51,6 +61,8 @@ class EmmentalAudio:
|
|||
self.playbin.disconnect(self.finish_cb)
|
||||
self.bus.disconnect(self.bus_cb)
|
||||
PauseButton.disconnect(self.pause_cb)
|
||||
PauseUp.disconnect(self.up_cb)
|
||||
PauseDown.disconnect(self.down_cb)
|
||||
PlayButton.disconnect(self.play_cb)
|
||||
NextButton.disconnect(self.next_cb)
|
||||
PrevButton.disconnect(self.prev_cb)
|
||||
|
@ -110,6 +122,39 @@ class EmmentalAudio:
|
|||
def pause(self, *args):
|
||||
self.playbin.set_state(Gst.State.PAUSED)
|
||||
|
||||
def pause_changed(self, state):
|
||||
text = f"{self.pause_count} Tracks"
|
||||
icon = "media-playback-pause"
|
||||
if self.pause_count == -1:
|
||||
if state == Gst.State.PLAYING:
|
||||
text = "Keep Playing"
|
||||
icon = "media-playback-start"
|
||||
else:
|
||||
text = "Paused"
|
||||
elif self.pause_count == 0:
|
||||
text = "This Track"
|
||||
elif self.pause_count == 1:
|
||||
text = "Next Track"
|
||||
|
||||
PauseEntry.set_text(text)
|
||||
PauseEntry.set_icon_from_icon_name(PauseIcon, icon)
|
||||
PauseDown.set_sensitive(self.pause_count > -1)
|
||||
PauseUp.set_sensitive(self.pause_count < 99)
|
||||
|
||||
def pause_input(self, entry):
|
||||
match = re.search("-?\d+", entry.get_text())
|
||||
if match:
|
||||
self.pause_count = max(-1, min(99, int(match.group(0))))
|
||||
self.pause_changed(self.playbin.get_state(Gst.Format.TIME)[1])
|
||||
|
||||
def pause_dec(self, *args):
|
||||
self.pause_count = max(-1, self.pause_count - 1)
|
||||
self.pause_changed(self.playbin.get_state(Gst.Format.TIME)[1])
|
||||
|
||||
def pause_inc(self, *args):
|
||||
self.pause_count = min(99, self.pause_count + 1)
|
||||
self.pause_changed(self.playbin.get_state(Gst.Format.TIME)[1])
|
||||
|
||||
def play(self, *args):
|
||||
self.playbin.set_state(Gst.State.PLAYING)
|
||||
|
||||
|
@ -152,6 +197,8 @@ class EmmentalAudio:
|
|||
if state == Gst.State.READY:
|
||||
Title.set_markup("<big>Emmental</big>")
|
||||
Artist.set_markup("<big>The Cheesy Music Player</big>")
|
||||
elif state in (Gst.State.PLAYING, Gst.State.PAUSED):
|
||||
self.pause_changed(state)
|
||||
curds.notify.notify("state-changed", state)
|
||||
|
||||
def update_progress(self):
|
||||
|
|
|
@ -10,7 +10,7 @@ Builder.add_from_file("emmental.ui")
|
|||
|
||||
UpArrow = Builder.get_object("up_arrow")
|
||||
DownArrow = Builder.get_object("down_arrow")
|
||||
PauseAfter = Builder.get_object("pause_after")
|
||||
PauseBox = Builder.get_object("pause_box")
|
||||
PlistSearch = Builder.get_object("playlist_search")
|
||||
ShowMore = Builder.get_object("show_more")
|
||||
TrackSearch = Builder.get_object("track_search")
|
||||
|
@ -72,7 +72,7 @@ def show_more_toggled(self, *args):
|
|||
active = ShowMore.get_active()
|
||||
UpArrow.set_visible(active)
|
||||
DownArrow.set_visible(not active)
|
||||
PauseAfter.set_visible(active)
|
||||
PauseBox.set_visible(active)
|
||||
PlistSearch.set_visible(active)
|
||||
TrackSearch.set_visible(active)
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ class TestGst(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
self.audio.playbin.set_state(Gst.State.READY)
|
||||
self.audio.seek(value=0)
|
||||
self.audio.pause_count = -1
|
||||
self.audio.pause_changed(Gst.State.PAUSED)
|
||||
gtk.main_loop(delay=0.1)
|
||||
self.audio.disconnect()
|
||||
|
||||
|
@ -41,7 +43,7 @@ class TestGst(unittest.TestCase):
|
|||
def on_stream_start(self):
|
||||
self.stream = True
|
||||
|
||||
def test_gst_init(self):
|
||||
def test_gst_types(self):
|
||||
self.assertIsInstance(self.audio.playbin, Gst.Element)
|
||||
self.assertIsInstance(self.audio.bus, Gst.Bus)
|
||||
|
||||
|
@ -50,6 +52,9 @@ class TestGst(unittest.TestCase):
|
|||
self.assertIsInstance(gst.Duration, Gtk.Label)
|
||||
self.assertIsInstance(gst.NextButton, Gtk.Button)
|
||||
self.assertIsInstance(gst.PauseButton, Gtk.Button)
|
||||
self.assertIsInstance(gst.PauseDown, Gtk.Button)
|
||||
self.assertIsInstance(gst.PauseEntry, Gtk.Entry)
|
||||
self.assertIsInstance(gst.PauseUp, Gtk.Button)
|
||||
self.assertIsInstance(gst.PlayButton, Gtk.Button)
|
||||
self.assertIsInstance(gst.Position, Gtk.Label)
|
||||
self.assertIsInstance(gst.PrevButton, Gtk.Button)
|
||||
|
@ -58,6 +63,9 @@ class TestGst(unittest.TestCase):
|
|||
self.assertIsInstance(gst.Volume, Gtk.VolumeButton)
|
||||
self.assertIsInstance(gst.VolumeAdj, Gtk.Adjustment)
|
||||
|
||||
self.assertEqual(gst.PauseIcon, Gtk.EntryIconPosition.PRIMARY)
|
||||
|
||||
def test_gst_init(self):
|
||||
self.assertEqual(gst.Title.get_text(), "Emmental")
|
||||
self.assertEqual(gst.Artist.get_text(), "The Cheesy Music Player")
|
||||
|
||||
|
@ -119,6 +127,9 @@ class TestGst(unittest.TestCase):
|
|||
curds.playlist.library.join()
|
||||
self.assertEqual(curds.PlaylistManager.lookup("Collection").current, -1)
|
||||
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Paused")
|
||||
self.assertEqual(gst.PauseEntry.get_icon_name(gst.PauseIcon), "media-playback-pause")
|
||||
|
||||
gst.NextButton.clicked()
|
||||
track1 = curds.PlaylistManager.track
|
||||
self.assertIsNotNone(track1)
|
||||
|
@ -133,17 +144,23 @@ class TestGst(unittest.TestCase):
|
|||
self.assertTrue( gst.PauseButton.is_visible())
|
||||
self.assertFalse(gst.PlayButton.is_visible())
|
||||
self.assertEqual(curds.PlaylistManager.track, track2)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Keep Playing")
|
||||
self.assertEqual(gst.PauseEntry.get_icon_name(gst.PauseIcon), "media-playback-start")
|
||||
|
||||
gst.PauseButton.clicked()
|
||||
self.main_loop_until(Gst.State.PAUSED)
|
||||
self.assertFalse(gst.PauseButton.is_visible())
|
||||
self.assertTrue( gst.PlayButton.is_visible())
|
||||
self.assertEqual(curds.PlaylistManager.track, track2)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Paused")
|
||||
self.assertEqual(gst.PauseEntry.get_icon_name(gst.PauseIcon), "media-playback-pause")
|
||||
|
||||
gst.PlayButton.clicked()
|
||||
self.main_loop_until(Gst.State.PLAYING)
|
||||
self.assertTrue( gst.PauseButton.is_visible())
|
||||
self.assertFalse(gst.PlayButton.is_visible())
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Keep Playing")
|
||||
self.assertEqual(gst.PauseEntry.get_icon_name(gst.PauseIcon), "media-playback-start")
|
||||
|
||||
gst.NextButton.clicked()
|
||||
self.main_loop_until(Gst.State.PLAYING)
|
||||
|
@ -166,6 +183,74 @@ class TestGst(unittest.TestCase):
|
|||
self.assertEqual(curds.PlaylistManager.lookup("Collection").current, 3)
|
||||
self.assertNotEqual(curds.PlaylistManager.track, track2)
|
||||
|
||||
def test_gst_automatic_pause_up(self):
|
||||
self.assertEqual(self.audio.pause_count, -1)
|
||||
self.assertFalse(gst.PauseDown.is_sensitive())
|
||||
self.assertTrue( gst.PauseUp.is_sensitive())
|
||||
|
||||
gst.PauseUp.clicked()
|
||||
self.assertEqual(self.audio.pause_count, 0)
|
||||
self.assertTrue( gst.PauseDown.is_sensitive())
|
||||
self.assertTrue( gst.PauseUp.is_sensitive())
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "This Track")
|
||||
self.assertEqual(gst.PauseEntry.get_icon_name(gst.PauseIcon), "media-playback-pause")
|
||||
|
||||
gst.PauseUp.clicked()
|
||||
self.assertEqual(self.audio.pause_count, 1)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Next Track")
|
||||
|
||||
self.audio.pause_count = 98
|
||||
gst.PauseUp.clicked()
|
||||
self.assertEqual(self.audio.pause_count, 99)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "99 Tracks")
|
||||
self.assertFalse(gst.PauseUp.is_sensitive())
|
||||
|
||||
gst.PauseUp.clicked()
|
||||
self.assertEqual(self.audio.pause_count, 99)
|
||||
|
||||
def test_gst_automatic_pause_down(self):
|
||||
self.assertEqual(self.audio.pause_count, -1)
|
||||
self.assertFalse(gst.PauseDown.is_sensitive())
|
||||
self.assertTrue( gst.PauseUp.is_sensitive())
|
||||
|
||||
self.audio.pause_count = 2
|
||||
gst.PauseDown.clicked()
|
||||
self.assertEqual(self.audio.pause_count, 1)
|
||||
self.assertTrue( gst.PauseDown.is_sensitive())
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Next Track")
|
||||
|
||||
gst.PauseDown.clicked()
|
||||
self.assertEqual(self.audio.pause_count, 0)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "This Track")
|
||||
|
||||
gst.PauseDown.clicked()
|
||||
self.assertEqual(self.audio.pause_count, -1)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Paused")
|
||||
self.assertFalse( gst.PauseDown.is_sensitive())
|
||||
|
||||
gst.PauseDown.clicked()
|
||||
self.assertEqual(self.audio.pause_count, -1)
|
||||
|
||||
def test_gst_automatic_pause_entry(self):
|
||||
gst.PauseEntry.set_text("-5")
|
||||
gst.PauseEntry.activate()
|
||||
self.assertEqual(self.audio.pause_count, -1)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "Paused")
|
||||
self.assertTrue( gst.PauseUp.is_sensitive())
|
||||
self.assertFalse(gst.PauseDown.is_sensitive())
|
||||
|
||||
gst.PauseEntry.set_text("3")
|
||||
gst.PauseEntry.activate()
|
||||
self.assertEqual(self.audio.pause_count, 3)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "3 Tracks")
|
||||
|
||||
gst.PauseEntry.set_text("100")
|
||||
gst.PauseEntry.activate()
|
||||
self.assertEqual(self.audio.pause_count, 99)
|
||||
self.assertEqual(gst.PauseEntry.get_text(), "99 Tracks")
|
||||
self.assertFalse(gst.PauseUp.is_sensitive())
|
||||
self.assertTrue( gst.PauseDown.is_sensitive())
|
||||
|
||||
def test_gst_seek(self):
|
||||
track = curds.Track.lookup(os.path.join(test_album, "10 - Test Track 10.ogg"))
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class TestGtk(unittest.TestCase):
|
|||
self.assertIsInstance(gtk.ShowMore, Gtk.ToggleButton)
|
||||
self.assertIsInstance(gtk.UpArrow, Gtk.Image)
|
||||
self.assertIsInstance(gtk.DownArrow, Gtk.Image)
|
||||
self.assertIsInstance(gtk.PauseAfter, Gtk.SpinButton)
|
||||
self.assertIsInstance(gtk.PauseBox, Gtk.Box)
|
||||
self.assertIsInstance(gtk.PlistSearch, Gtk.SearchEntry)
|
||||
self.assertIsInstance(gtk.TrackSearch, Gtk.SearchEntry)
|
||||
|
||||
|
@ -56,7 +56,7 @@ class TestGtk(unittest.TestCase):
|
|||
self.assertTrue( gtk.DownArrow.is_visible())
|
||||
self.assertFalse(gtk.PlistSearch.is_visible())
|
||||
self.assertFalse(gtk.TrackSearch.is_visible())
|
||||
self.assertFalse(gtk.PauseAfter.is_visible())
|
||||
self.assertFalse(gtk.PauseBox.is_visible())
|
||||
|
||||
gtk.ShowMore.set_active(True)
|
||||
self.assertTrue( gtk.ShowMore.get_active())
|
||||
|
@ -64,7 +64,7 @@ class TestGtk(unittest.TestCase):
|
|||
self.assertFalse(gtk.DownArrow.is_visible())
|
||||
self.assertTrue( gtk.PlistSearch.is_visible())
|
||||
self.assertTrue( gtk.TrackSearch.is_visible())
|
||||
self.assertTrue(gtk.PauseAfter.is_visible())
|
||||
self.assertTrue(gtk.PauseBox.is_visible())
|
||||
|
||||
gtk.ShowMore.set_active(False)
|
||||
self.assertFalse(gtk.ShowMore.get_active())
|
||||
|
@ -72,7 +72,7 @@ class TestGtk(unittest.TestCase):
|
|||
self.assertTrue( gtk.DownArrow.is_visible())
|
||||
self.assertFalse(gtk.PlistSearch.is_visible())
|
||||
self.assertFalse(gtk.TrackSearch.is_visible())
|
||||
self.assertFalse(gtk.PauseAfter.is_visible())
|
||||
self.assertFalse(gtk.PauseBox.is_visible())
|
||||
|
||||
def test_accel(self):
|
||||
play_button = gtk.Builder.get_object("play_button")
|
||||
|
|
Loading…
Reference in New Issue