tracklist: Add keyboard shortcuts
The following shortcuts are implemented: - Escape to unselect any selected tracks - Delete to remove selected tracks from the current playlist - <Control>/ to focus the "filter tracks" entry - <Control>l to cycle the loop state of the current playlist - <Control>s to toggle the shuffle state of the current playlist - <Control>Up to move the selected track up one position - <Control>Down to move the selected track down one position I also change the volume up and down shortcuts to use the <Shift> modifier. This matches how other Header shortcuts are triggered, and frees up the non-shifted versions to use here. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
9cf980d967
commit
717fdf39cd
|
@ -195,6 +195,8 @@ class Application(Adw.Application):
|
||||||
self.db.settings.bind_setting(f"tracklist.{name}.visible",
|
self.db.settings.bind_setting(f"tracklist.{name}.visible",
|
||||||
column, "visible")
|
column, "visible")
|
||||||
self.factory.bind_property("visible-playlist", track_list, "playlist")
|
self.factory.bind_property("visible-playlist", track_list, "playlist")
|
||||||
|
|
||||||
|
self.__add_accelerators(track_list.accelerators)
|
||||||
return track_list
|
return track_list
|
||||||
|
|
||||||
def build_window(self) -> window.Window:
|
def build_window(self) -> window.Window:
|
||||||
|
|
|
@ -124,9 +124,9 @@ class Header(Gtk.HeaderBar):
|
||||||
"""Get a list of accelerators for the Header."""
|
"""Get a list of accelerators for the Header."""
|
||||||
res = [ActionEntry("open-file", self._open.activate, "<Control>o"),
|
res = [ActionEntry("open-file", self._open.activate, "<Control>o"),
|
||||||
ActionEntry("decrease-volume", self._volume.decrement,
|
ActionEntry("decrease-volume", self._volume.decrement,
|
||||||
"<Control>Down"),
|
"<Shift><Control>Down"),
|
||||||
ActionEntry("increase-volume", self._volume.increment,
|
ActionEntry("increase-volume", self._volume.increment,
|
||||||
"<Control>Up"),
|
"<Shift><Control>Up"),
|
||||||
ActionEntry("toggle-bg-mode", self._background.activate,
|
ActionEntry("toggle-bg-mode", self._background.activate,
|
||||||
"<Shift><Control>b")]
|
"<Shift><Control>b")]
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
|
|
@ -4,6 +4,7 @@ from gi.repository import GObject
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
from ..action import ActionEntry
|
||||||
from ..playlist.playlist import Playlist
|
from ..playlist.playlist import Playlist
|
||||||
from ..playlist.previous import Previous
|
from ..playlist.previous import Previous
|
||||||
from .. import db
|
from .. import db
|
||||||
|
@ -143,3 +144,18 @@ class Card(Gtk.Box):
|
||||||
self._top_right.set_sensitive(not isinstance(newval, Previous))
|
self._top_right.set_sensitive(not isinstance(newval, Previous))
|
||||||
self.__set_button_state()
|
self.__set_button_state()
|
||||||
newval.connect("notify", self.__playlist_notify)
|
newval.connect("notify", self.__playlist_notify)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accelerators(self) -> list[ActionEntry]:
|
||||||
|
"""Get a list of accelerators for the Tracklist."""
|
||||||
|
return [ActionEntry("focus-search-track", self._filter.grab_focus,
|
||||||
|
"<Control>slash"),
|
||||||
|
ActionEntry("clear-selected-tracks", self._unselect.activate,
|
||||||
|
"Escape", enabled=(self._unselect, "sensitive")),
|
||||||
|
ActionEntry("cycle-loop", self._loop.activate,
|
||||||
|
"<Control>l", enabled=(self._top_right,
|
||||||
|
"sensitive")),
|
||||||
|
ActionEntry("toggle-shuffle", self._shuffle.activate,
|
||||||
|
"<Control>s", enabled=(self._top_right,
|
||||||
|
"sensitive"))] + \
|
||||||
|
self._osd.accelerators
|
||||||
|
|
|
@ -4,6 +4,7 @@ from gi.repository import GObject
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Adw
|
from gi.repository import Adw
|
||||||
|
from ..action import ActionEntry
|
||||||
from ..buttons import PopoverButton
|
from ..buttons import PopoverButton
|
||||||
from .. import db
|
from .. import db
|
||||||
from .. import playlist
|
from .. import playlist
|
||||||
|
@ -257,3 +258,15 @@ class OSD(Gtk.Overlay):
|
||||||
self.__selection_changed(self.selection, 0, 0)
|
self.__selection_changed(self.selection, 0, 0)
|
||||||
if self.playlist is not None:
|
if self.playlist is not None:
|
||||||
self._add.popover_child.playlist = self.playlist.playlist
|
self._add.popover_child.playlist = self.playlist.playlist
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accelerators(self) -> list[ActionEntry]:
|
||||||
|
"""Get a list of accelerators for the OSD."""
|
||||||
|
return [ActionEntry("remove-selected-tracks", self._remove.activate,
|
||||||
|
"Delete", enabled=(self._remove, "visible")),
|
||||||
|
ActionEntry("move-track-up", self._move._up.activate,
|
||||||
|
"<Control>Up",
|
||||||
|
enabled=(self._move, "can-move-up")),
|
||||||
|
ActionEntry("move-track-down", self._move._down.activate,
|
||||||
|
"<Control>Down",
|
||||||
|
enabled=(self._move, "can-move-down"))]
|
||||||
|
|
|
@ -185,9 +185,9 @@ class TestHeader(tests.util.TestCase):
|
||||||
"""Check that the accelerators list is set up properly."""
|
"""Check that the accelerators list is set up properly."""
|
||||||
entries = [("open-file", self.header._open.activate, "<Control>o"),
|
entries = [("open-file", self.header._open.activate, "<Control>o"),
|
||||||
("decrease-volume", self.header._volume.decrement,
|
("decrease-volume", self.header._volume.decrement,
|
||||||
"<Control>Down"),
|
"<Shift><Control>Down"),
|
||||||
("increase-volume", self.header._volume.increment,
|
("increase-volume", self.header._volume.increment,
|
||||||
"<Control>Up"),
|
"<Shift><Control>Up"),
|
||||||
("toggle-bg-mode", self.header._background.activate,
|
("toggle-bg-mode", self.header._background.activate,
|
||||||
"<Shift><Control>b"),
|
"<Shift><Control>b"),
|
||||||
("edit-settings", self.header._settings.activate,
|
("edit-settings", self.header._settings.activate,
|
||||||
|
|
|
@ -128,8 +128,8 @@ class TestEmmental(unittest.TestCase):
|
||||||
self.application.build_window()
|
self.application.build_window()
|
||||||
|
|
||||||
for action, accel in [("app.open-file", "<Control>o"),
|
for action, accel in [("app.open-file", "<Control>o"),
|
||||||
("app.decrease-volume", "<Control>Down"),
|
("app.decrease-volume", "<Shift><Control>Down"),
|
||||||
("app.increase-volume", "<Control>Up"),
|
("app.increase-volume", "<Shift><Control>Up"),
|
||||||
("app.toggle-bg-mode", "<Shift><Control>b"),
|
("app.toggle-bg-mode", "<Shift><Control>b"),
|
||||||
("app.edit-settings", "<Shift><Control>s")]:
|
("app.edit-settings", "<Shift><Control>s")]:
|
||||||
self.assertEqual(self.application.get_accels_for_action(action),
|
self.assertEqual(self.application.get_accels_for_action(action),
|
||||||
|
@ -227,6 +227,16 @@ class TestEmmental(unittest.TestCase):
|
||||||
self.application.player = emmental.audio.Player()
|
self.application.player = emmental.audio.Player()
|
||||||
win = self.application.build_window()
|
win = self.application.build_window()
|
||||||
|
|
||||||
|
for action, accel in [("app.focus-search-track", "<Control>slash"),
|
||||||
|
("app.clear-selected-tracks", "Escape"),
|
||||||
|
("app.cycle-loop", "<Control>l"),
|
||||||
|
("app.toggle-shuffle", "<Control>s"),
|
||||||
|
("app.remove-selected-tracks", "Delete"),
|
||||||
|
("app.move-track-up", "<Control>Up"),
|
||||||
|
("app.move-track-down", "<Control>Down")]:
|
||||||
|
self.assertEqual(self.application.get_accels_for_action(action),
|
||||||
|
[accel])
|
||||||
|
|
||||||
self.assertEqual(win.tracklist.sql, self.application.db)
|
self.assertEqual(win.tracklist.sql, self.application.db)
|
||||||
|
|
||||||
playlist = self.application.db.playlists.create("Test Playlist")
|
playlist = self.application.db.playlists.create("Test Playlist")
|
||||||
|
|
|
@ -456,3 +456,28 @@ class TestOsd(tests.util.TestCase):
|
||||||
self.osd.reset()
|
self.osd.reset()
|
||||||
mock_unselect.assert_called()
|
mock_unselect.assert_called()
|
||||||
self.assertFalse(self.osd.have_selected)
|
self.assertFalse(self.osd.have_selected)
|
||||||
|
|
||||||
|
def test_accelerators(self):
|
||||||
|
"""Test that the accelerators list is set up properly."""
|
||||||
|
entries = [("remove-selected-tracks", self.osd._remove.activate,
|
||||||
|
["Delete"], self.osd._remove, "visible"),
|
||||||
|
("move-track-up", self.osd._move._up.activate,
|
||||||
|
["<Control>Up"], self.osd._move, "can-move-up"),
|
||||||
|
("move-track-down", self.osd._move._down.activate,
|
||||||
|
["<Control>Down"], self.osd._move, "can-move-down")]
|
||||||
|
|
||||||
|
accels = self.osd.accelerators
|
||||||
|
self.assertIsInstance(accels, list)
|
||||||
|
|
||||||
|
for i, (name, func, accel, gobject, prop) in enumerate(entries):
|
||||||
|
with self.subTest(action=name):
|
||||||
|
self.assertIsInstance(accels[i], emmental.action.ActionEntry)
|
||||||
|
self.assertEqual(accels[i].name, name)
|
||||||
|
self.assertEqual(accels[i].func, func)
|
||||||
|
self.assertEqual(accels[i].accels, accel)
|
||||||
|
|
||||||
|
if gobject and prop:
|
||||||
|
enabled = gobject.get_property(prop)
|
||||||
|
self.assertEqual(accels[i].enabled, enabled)
|
||||||
|
gobject.set_property(prop, not enabled)
|
||||||
|
self.assertEqual(accels[i].enabled, not enabled)
|
||||||
|
|
|
@ -250,3 +250,40 @@ class TestTracklist(tests.util.TestCase):
|
||||||
self.assertEqual(self.tracklist._Card__scroll_idle(None),
|
self.assertEqual(self.tracklist._Card__scroll_idle(None),
|
||||||
GLib.SOURCE_REMOVE)
|
GLib.SOURCE_REMOVE)
|
||||||
mock_scroll.assert_called_with(None)
|
mock_scroll.assert_called_with(None)
|
||||||
|
|
||||||
|
def test_accelerators(self):
|
||||||
|
"""Check that the accelerators list is set up properly."""
|
||||||
|
entries = [("focus-search-track", self.tracklist._filter.grab_focus,
|
||||||
|
["<Control>slash"], None, None),
|
||||||
|
("clear-selected-tracks", self.tracklist._unselect.activate,
|
||||||
|
["Escape"], self.tracklist._unselect, "sensitive"),
|
||||||
|
("cycle-loop", self.tracklist._loop.activate,
|
||||||
|
["<Control>l"], self.tracklist._top_right, "sensitive"),
|
||||||
|
("toggle-shuffle", self.tracklist._shuffle.activate,
|
||||||
|
["<Control>s"], self.tracklist._top_right, "sensitive")]
|
||||||
|
|
||||||
|
accels = self.tracklist.accelerators
|
||||||
|
self.assertIsInstance(accels, list)
|
||||||
|
|
||||||
|
for i, (name, func, accel, gobject, prop) in enumerate(entries):
|
||||||
|
with self.subTest(action=name):
|
||||||
|
self.assertIsInstance(accels[i], emmental.action.ActionEntry)
|
||||||
|
self.assertEqual(accels[i].name, name)
|
||||||
|
self.assertEqual(accels[i].func, func)
|
||||||
|
self.assertListEqual(accels[i].accels, accel)
|
||||||
|
|
||||||
|
if gobject and prop:
|
||||||
|
enabled = gobject.get_property(prop)
|
||||||
|
self.assertEqual(accels[i].enabled, enabled)
|
||||||
|
gobject.set_property(prop, not enabled)
|
||||||
|
self.assertEqual(accels[i].enabled, not enabled)
|
||||||
|
|
||||||
|
start = len(entries)
|
||||||
|
osd_accels = self.tracklist._osd.accelerators
|
||||||
|
for i, accel in enumerate(osd_accels):
|
||||||
|
with self.subTest(name=accel.name):
|
||||||
|
self.assertIsInstance(accels[start + i],
|
||||||
|
emmental.action.ActionEntry)
|
||||||
|
self.assertEqual(accels[start + i].name, accel.name)
|
||||||
|
self.assertEqual(accels[start + i].func, accel.func)
|
||||||
|
self.assertListEqual(accels[start + i].accels, accel.accels)
|
||||||
|
|
Loading…
Reference in New Issue