tracklist: Add Move Down and Move Up buttons to the OSD
These are used to manually rearrange the Tracks in the Playlist. The buttons are only marked sensitive if one item is selected. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
a6f59d9378
commit
78ea2904a1
|
@ -85,3 +85,7 @@ columnview.emmental-track-list > listview > row > cell > picture {
|
|||
min-width: 36px;
|
||||
border-radius: 15%;
|
||||
}
|
||||
|
||||
box.emmental-move-buttons > button > image {
|
||||
color: @accent_color;
|
||||
}
|
||||
|
|
|
@ -105,6 +105,50 @@ class PlaylistView(Gtk.ListView):
|
|||
"""Signal that the user has selected a Playlist."""
|
||||
|
||||
|
||||
class MoveButtons(Gtk.Box):
|
||||
"""Buttons for moving Tracks in the playlist."""
|
||||
|
||||
can_move_down = GObject.Property(type=bool, default=False)
|
||||
can_move_up = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
"""Initialize the Move Buttons."""
|
||||
super().__init__(**kwargs)
|
||||
self._down = Gtk.Button(icon_name="go-down-symbolic",
|
||||
hexpand=True, sensitive=False)
|
||||
self._up = Gtk.Button(icon_name="go-up-symbolic",
|
||||
hexpand=True, sensitive=False)
|
||||
|
||||
self.bind_property("can-move-down", self._down, "sensitive")
|
||||
self.bind_property("can-move-up", self._up, "sensitive")
|
||||
|
||||
self._down.connect("clicked", self.__clicked, "move-down")
|
||||
self._up.connect("clicked", self.__clicked, "move-up")
|
||||
|
||||
self._down.add_css_class("opaque")
|
||||
self._down.add_css_class("pill")
|
||||
self._up.add_css_class("opaque")
|
||||
self._up.add_css_class("pill")
|
||||
|
||||
self.add_css_class("emmental-move-buttons")
|
||||
self.add_css_class("large-icons")
|
||||
self.add_css_class("linked")
|
||||
|
||||
self.append(self._up)
|
||||
self.append(self._down)
|
||||
|
||||
def __clicked(self, button: Gtk.Button, signal: str) -> None:
|
||||
self.emit(signal)
|
||||
|
||||
@GObject.Signal
|
||||
def move_down(self) -> None:
|
||||
"""Signal that the move down button was clicked."""
|
||||
|
||||
@GObject.Signal
|
||||
def move_up(self) -> None:
|
||||
"""Signal that the move up button was clicked."""
|
||||
|
||||
|
||||
class OSD(Gtk.Overlay):
|
||||
"""An Overlay with extra controls for the Tracklist."""
|
||||
|
||||
|
@ -129,6 +173,9 @@ class OSD(Gtk.Overlay):
|
|||
halign=Gtk.Align.END, valign=Gtk.Align.END,
|
||||
margin_end=16, margin_bottom=16,
|
||||
visible=False)
|
||||
self._move = MoveButtons(halign=Gtk.Align.CENTER, valign=Gtk.Align.END,
|
||||
margin_bottom=16, sensitive=False,
|
||||
visible=False)
|
||||
self._sizegroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
|
||||
|
||||
self._add.add_css_class("suggested-action")
|
||||
|
@ -139,12 +186,16 @@ class OSD(Gtk.Overlay):
|
|||
self.selection.connect("selection-changed", self.__selection_changed)
|
||||
self._add.popover_child.connect("playlist-selected", self.__add_tracks)
|
||||
self._remove.connect("clicked", self.__remove_clicked)
|
||||
self._move.connect("move-down", self.__move_track_down)
|
||||
self._move.connect("move-up", self.__move_track_up)
|
||||
|
||||
self._sizegroup.add_widget(self._add)
|
||||
self._sizegroup.add_widget(self._remove)
|
||||
self._sizegroup.add_widget(self._move)
|
||||
|
||||
self.add_overlay(self._add)
|
||||
self.add_overlay(self._remove)
|
||||
self.add_overlay(self._move)
|
||||
|
||||
def __get_selected_tracks(self) -> list:
|
||||
selection = self.selection.get_selection()
|
||||
|
@ -163,6 +214,22 @@ class OSD(Gtk.Overlay):
|
|||
self.playlist.remove_track(track)
|
||||
self.clear_selection()
|
||||
|
||||
def __move_track_down(self, move: MoveButtons) -> None:
|
||||
if self.playlist is not None:
|
||||
index = self.selection.get_selection().get_nth(0)
|
||||
self.selection.get_model().set_incremental(False)
|
||||
self.playlist.move_track_down(self.selection[index])
|
||||
self.selection.get_model().set_incremental(True)
|
||||
self.__update_visibility()
|
||||
|
||||
def __move_track_up(self, move: MoveButtons) -> None:
|
||||
if self.playlist is not None:
|
||||
index = self.selection.get_selection().get_nth(0)
|
||||
self.selection.get_model().set_incremental(False)
|
||||
self.playlist.move_track_up(self.selection[index])
|
||||
self.selection.get_model().set_incremental(True)
|
||||
self.__update_visibility()
|
||||
|
||||
def __selection_changed(self, selection: Gtk.SelectionModel,
|
||||
position: int, n_items: int) -> None:
|
||||
self.n_selected = selection.get_selection().get_size()
|
||||
|
@ -172,9 +239,20 @@ class OSD(Gtk.Overlay):
|
|||
def __update_visibility(self) -> None:
|
||||
db_plist = None if self.playlist is None else self.playlist.playlist
|
||||
user = False if db_plist is None else db_plist.user_tracks
|
||||
movable = False if db_plist is None else db_plist.tracks_movable
|
||||
|
||||
self._add.set_visible(db_plist is not None and self.have_selected)
|
||||
self._remove.set_visible(user and self.have_selected)
|
||||
|
||||
self._move.set_visible(movable and self.have_selected)
|
||||
if self.n_selected == 1:
|
||||
index = self.selection.get_selection().get_nth(0)
|
||||
self._move.set_sensitive(True)
|
||||
self._move.can_move_down = index < len(self.selection) - 1
|
||||
self._move.can_move_up = index > 0
|
||||
else:
|
||||
self._move.set_sensitive(False)
|
||||
|
||||
def clear_selection(self, *args) -> None:
|
||||
"""Clear the current selection."""
|
||||
self.selection.unselect_all()
|
||||
|
|
|
@ -166,6 +166,71 @@ class TestPlaylistView(tests.util.TestCase):
|
|||
selected.assert_called_with(self.view, self.sql.playlists.favorites)
|
||||
|
||||
|
||||
class TestMoveButtons(unittest.TestCase):
|
||||
"""Test the Tracklist move up & down buttons."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.move = emmental.tracklist.selection.MoveButtons()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the move buttons were set up properly."""
|
||||
self.assertIsInstance(self.move, Gtk.Box)
|
||||
self.assertTrue(self.move.has_css_class("emmental-move-buttons"))
|
||||
self.assertTrue(self.move.has_css_class("large-icons"))
|
||||
self.assertTrue(self.move.has_css_class("linked"))
|
||||
|
||||
def test_move_down(self):
|
||||
"""Test the move down button."""
|
||||
self.assertIsInstance(self.move._down, Gtk.Button)
|
||||
self.assertEqual(self.move._down.get_icon_name(), "go-down-symbolic")
|
||||
self.assertTrue(self.move._down.has_css_class("opaque"))
|
||||
self.assertTrue(self.move._down.has_css_class("pill"))
|
||||
self.assertTrue(self.move._down.get_hexpand())
|
||||
|
||||
move_down = unittest.mock.Mock()
|
||||
self.move.connect("move-down", move_down)
|
||||
self.move._down.emit("clicked")
|
||||
move_down.assert_called()
|
||||
|
||||
self.assertEqual(self.move._up.get_next_sibling(), self.move._down)
|
||||
|
||||
def test_can_move_down(self):
|
||||
"""Test the can-move-down property."""
|
||||
self.assertFalse(self.move.can_move_down)
|
||||
self.assertFalse(self.move._down.get_sensitive())
|
||||
|
||||
self.move.can_move_down = True
|
||||
self.assertTrue(self.move._down.get_sensitive())
|
||||
self.move.can_move_down = False
|
||||
self.assertFalse(self.move._down.get_sensitive())
|
||||
|
||||
def test_move_up(self):
|
||||
"""Test the move up button."""
|
||||
self.assertIsInstance(self.move._up, Gtk.Button)
|
||||
self.assertEqual(self.move._up.get_icon_name(), "go-up-symbolic")
|
||||
self.assertTrue(self.move._up.has_css_class("opaque"))
|
||||
self.assertTrue(self.move._up.has_css_class("pill"))
|
||||
self.assertTrue(self.move._up.get_hexpand())
|
||||
|
||||
move_up = unittest.mock.Mock()
|
||||
self.move.connect("move-up", move_up)
|
||||
self.move._up.emit("clicked")
|
||||
move_up.assert_called()
|
||||
|
||||
self.assertEqual(self.move.get_first_child(), self.move._up)
|
||||
|
||||
def test_can_move_up(self):
|
||||
"""Test the can-move-up property."""
|
||||
self.assertFalse(self.move.can_move_up)
|
||||
self.assertFalse(self.move._up.get_sensitive())
|
||||
|
||||
self.move.can_move_up = True
|
||||
self.assertTrue(self.move._up.get_sensitive())
|
||||
self.move.can_move_up = False
|
||||
self.assertFalse(self.move._up.get_sensitive())
|
||||
|
||||
|
||||
class TestOsd(tests.util.TestCase):
|
||||
"""Test the Tracklist OSD."""
|
||||
|
||||
|
@ -287,6 +352,77 @@ class TestOsd(tests.util.TestCase):
|
|||
unittest.mock.call(self.model[1]),
|
||||
unittest.mock.call(self.model[2])])
|
||||
|
||||
def test_move_buttons(self):
|
||||
"""Test the move buttons."""
|
||||
self.assertIsInstance(self.osd._move,
|
||||
emmental.tracklist.selection.MoveButtons)
|
||||
self.assertEqual(self.osd._move.get_halign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(self.osd._move.get_valign(), Gtk.Align.END)
|
||||
self.assertEqual(self.osd._move.get_margin_bottom(), 16)
|
||||
|
||||
self.assertIn(self.osd._move, self.osd._sizegroup.get_widgets())
|
||||
self.assertIn(self.osd._move, self.osd)
|
||||
|
||||
def test_move_button_clicks(self):
|
||||
"""Test clicking the move buttons."""
|
||||
set_incremental = unittest.mock.Mock()
|
||||
self.model.set_incremental = set_incremental
|
||||
self.osd.playlist = self.playlist
|
||||
self.selection.select_item(1, True)
|
||||
|
||||
with unittest.mock.patch.object(self.playlist,
|
||||
"move_track_down") as mock_move_down:
|
||||
self.osd._move.emit("move-down")
|
||||
mock_move_down.assert_called_with(self.model[1])
|
||||
set_incremental.assert_has_calls([unittest.mock.call(False),
|
||||
unittest.mock.call(True)])
|
||||
|
||||
set_incremental.reset_mock()
|
||||
with unittest.mock.patch.object(self.playlist,
|
||||
"move_track_up") as mock_move_up:
|
||||
self.osd._move.emit("move-up")
|
||||
mock_move_up.assert_called_with(self.model[1])
|
||||
set_incremental.assert_has_calls([unittest.mock.call(False),
|
||||
unittest.mock.call(True)])
|
||||
|
||||
def test_move_buttons_sensitive(self):
|
||||
"""Test the move button sensitivity."""
|
||||
self.assertFalse(self.osd._move.get_sensitive())
|
||||
|
||||
self.osd.playlist = self.playlist
|
||||
self.selection.select_item(0, True)
|
||||
self.assertTrue(self.osd._move.get_sensitive())
|
||||
self.assertTrue(self.osd._move.can_move_down)
|
||||
self.assertFalse(self.osd._move.can_move_up)
|
||||
|
||||
self.selection.select_item(1, True)
|
||||
self.assertTrue(self.osd._move.get_sensitive())
|
||||
self.assertTrue(self.osd._move.can_move_down)
|
||||
self.assertTrue(self.osd._move.can_move_up)
|
||||
|
||||
self.selection.select_item(2, True)
|
||||
self.assertTrue(self.osd._move.get_sensitive())
|
||||
self.assertFalse(self.osd._move.can_move_down)
|
||||
self.assertTrue(self.osd._move.can_move_up)
|
||||
|
||||
self.selection.select_item(1, False)
|
||||
self.assertFalse(self.osd._move.get_sensitive())
|
||||
|
||||
def test_move_buttons_visible(self):
|
||||
"""Test the move button visibility."""
|
||||
self.assertFalse(self.osd._move.get_visible())
|
||||
|
||||
self.selection.select_item(0, True)
|
||||
self.assertFalse(self.osd._move.get_visible())
|
||||
|
||||
self.osd.playlist = self.playlist
|
||||
self.selection.select_item(1, True)
|
||||
self.assertTrue(self.osd._move.get_visible())
|
||||
|
||||
self.db_plist.tracks_movable = False
|
||||
self.selection.select_item(2, True)
|
||||
self.assertFalse(self.osd._move.get_visible())
|
||||
|
||||
def test_selection_properties(self):
|
||||
"""Test updating properties when the selection changes."""
|
||||
self.assertFalse(self.osd.have_selected)
|
||||
|
|
Loading…
Reference in New Issue