From a944af7f3e52fd1c616a30c0537cddc4e336c6fa Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Wed, 24 Jan 2024 20:38:41 -0500 Subject: [PATCH] header: Convert the Open button to an Adw.ActionRow And put it in the new Menu button popover list. I don't expect this to be a common action, so the extra button press is acceptable. Signed-off-by: Anna Schumaker --- emmental/header/__init__.py | 6 +-- emmental/header/open.py | 22 +++++++---- tests/header/test_header.py | 9 ++++- tests/header/test_open.py | 73 +++++++++++++++++++++---------------- 4 files changed, 66 insertions(+), 44 deletions(-) diff --git a/emmental/header/__init__.py b/emmental/header/__init__.py index efc5b3d..8f90f6c 100644 --- a/emmental/header/__init__.py +++ b/emmental/header/__init__.py @@ -49,10 +49,11 @@ class Header(Gtk.HeaderBar): icon = "sidebar-show-symbolic" self._show_sidebar = Gtk.ToggleButton(icon_name=icon, has_frame=False) - self._open = open.Button() + self._open = open.OpenRow() self._menu_box = Gtk.ListBox(selection_mode=Gtk.SelectionMode.NONE) self._menu_box.add_css_class("boxed-list") + self._menu_box.append(self._open) icon = "open-menu-symbolic" self._menu_button = buttons.PopoverButton(popover_child=self._menu_box, @@ -94,7 +95,6 @@ class Header(Gtk.HeaderBar): GObject.BindingFlags.BIDIRECTIONAL) self.pack_start(self._show_sidebar) - self.pack_start(self._open) if __debug__: self._window = settings.Window(sql) self._settings = Gtk.Button(icon_name="settings-symbolic", @@ -131,7 +131,7 @@ class Header(Gtk.HeaderBar): f"normalizing: {rg_status}") self._vol_button.set_tooltip_text(status) - def __track_requested(self, button: open.Button, + def __track_requested(self, button: open.OpenRow, path: pathlib.Path) -> None: self.emit("track-requested", path) diff --git a/emmental/header/open.py b/emmental/header/open.py index 2e9efd4..f3cb3bf 100644 --- a/emmental/header/open.py +++ b/emmental/header/open.py @@ -1,19 +1,21 @@ # Copyright 2023 (c) Anna Schumaker. -"""A custom Button that opens a FileDialog to select a file for playback.""" +"""A custom Adw.ActionRow to select a file for playback.""" import pathlib from gi.repository import GObject from gi.repository import GLib from gi.repository import Gio from gi.repository import Gtk +from gi.repository import Adw -class Button(Gtk.Button): - """Our pre-configured open button.""" +class OpenRow(Adw.ActionRow): + """Our pre-configured open Adw.ActionRow.""" def __init__(self): - """Initialize our open button.""" - super().__init__(icon_name="document-open-symbolic", - tooltip_text="open a file for playback") + """Initialize our open ActionRow.""" + super().__init__(activatable=True, title="Open File", + subtitle="Select a file for playback") + self._prefix = Gtk.Image(icon_name="document-open-symbolic") self._filters = Gio.ListStore() self._filter = Gtk.FileFilter(name="Audio Files", mime_types=["inode/directory", @@ -23,6 +25,9 @@ class Button(Gtk.Button): self._filters.append(self._filter) + self.connect("activated", self.__on_activated) + self.add_prefix(self._prefix) + def __async_ready(self, dialog: Gtk.FileDialog, task: Gio.Task) -> None: try: file = dialog.open_finish(task) @@ -30,8 +35,9 @@ class Button(Gtk.Button): except GLib.Error: pass - def do_clicked(self) -> None: - """Handle a click event.""" + def __on_activated(self, row: Adw.ActionRow) -> None: + """Handle activating an OpenRow.""" + self.get_ancestor(Gtk.Popover).popdown() self._dialog.open(self.get_ancestor(Gtk.Window), None, self.__async_ready) diff --git a/tests/header/test_header.py b/tests/header/test_header.py index b7b4b51..dcadb49 100644 --- a/tests/header/test_header.py +++ b/tests/header/test_header.py @@ -52,8 +52,10 @@ class TestHeader(tests.util.TestCase): self.assertFalse(self.header.show_sidebar) def test_open(self): - """Check that the Open button works as expected.""" - self.assertIsInstance(self.header._open, emmental.header.open.Button) + """Check that the Open ActionRow works as expected.""" + self.assertIsInstance(self.header._open, emmental.header.open.OpenRow) + self.assertEqual(self.header._menu_box.get_row_at_index(0), + self.header._open) signal = unittest.mock.Mock() self.header.connect("track-requested", signal) @@ -95,6 +97,9 @@ class TestHeader(tests.util.TestCase): Gtk.SelectionMode.NONE) self.assertTrue(self.header._menu_box.has_css_class("boxed-list")) + self.assertEqual(self.header._menu_box.get_row_at_index(0), + self.header._open) + def test_volume_icons(self): """Check that the volume icons box is set up properly.""" self.assertIsInstance(self.header._icons, Gtk.Box) diff --git a/tests/header/test_open.py b/tests/header/test_open.py index c6bca1e..c32d664 100644 --- a/tests/header/test_open.py +++ b/tests/header/test_open.py @@ -1,58 +1,69 @@ # Copyright 2023 (c) Anna Schumaker. -"""Tests our Open button.""" +"""Tests our Open Adw.ActionRow.""" import emmental.header.open import pathlib import unittest from gi.repository import Gio from gi.repository import Gtk +from gi.repository import Adw -class TestButton(unittest.TestCase): - """Test the Open button.""" +class TestOpenRow(unittest.TestCase): + """Test the Open row.""" def setUp(self): """Set up common variables.""" - self.button = emmental.header.open.Button() + self.row = emmental.header.open.OpenRow() - def test_button(self): - """Check that the button was set up properly.""" - self.assertIsInstance(self.button, Gtk.Button) - self.assertEqual(self.button.get_icon_name(), "document-open-symbolic") - self.assertEqual(self.button.get_tooltip_text(), - "open a file for playback") + def test_action_row(self): + """Check that the action row was set up properly.""" + self.assertIsInstance(self.row, Adw.ActionRow) + self.assertIsInstance(self.row._prefix, Gtk.Image) + + self.assertEqual(self.row.props.title, "Open File") + self.assertEqual(self.row.props.subtitle, "Select a file for playback") + self.assertTrue(self.row.props.activatable) + + self.assertEqual(self.row._prefix.props.icon_name, + "document-open-symbolic") def test_filter(self): """Check that the file filter is set up properly.""" - self.assertIsInstance(self.button._filter, Gtk.FileFilter) - self.assertIsInstance(self.button._filters, Gio.ListStore) + self.assertIsInstance(self.row._filter, Gtk.FileFilter) + self.assertIsInstance(self.row._filters, Gio.ListStore) - self.assertEqual(self.button._filter.get_name(), "Audio Files") - self.assertEqual(self.button._filters[0], self.button._filter) + self.assertEqual(self.row._filter.get_name(), "Audio Files") + self.assertEqual(self.row._filters[0], self.row._filter) def test_dialog(self): """Check that the file dialog is set up properly.""" - self.assertIsInstance(self.button._dialog, Gtk.FileDialog) - self.assertEqual(self.button._dialog.get_title(), "Pick a Track") - self.assertEqual(self.button._dialog.get_filters(), - self.button._filters) - self.assertTrue(self.button._dialog.get_modal()) + self.assertIsInstance(self.row._dialog, Gtk.FileDialog) + self.assertEqual(self.row._dialog.get_title(), "Pick a Track") + self.assertEqual(self.row._dialog.get_filters(), + self.row._filters) + self.assertTrue(self.row._dialog.get_modal()) - def test_clicked(self): - """Test clicking on the button.""" - with unittest.mock.patch.object(self.button._dialog, - "open") as mock_open: - self.button.emit("clicked") - mock_open.assert_called_with(None, None, - self.button._Button__async_ready) + def test_activate(self): + """Test activating an OpenRow.""" + listbox = Gtk.ListBox() + popover = Gtk.Popover(child=listbox) + listbox.append(self.row) - with unittest.mock.patch.object(self.button._dialog, + with unittest.mock.patch.object(popover, "popdown") as mock_popdown: + with unittest.mock.patch.object(self.row._dialog, + "open") as mock_open: + self.row.emit("activated") + mock_popdown.assert_called() + mock_open.assert_called_with(None, None, + self.row._OpenRow__async_ready) + + with unittest.mock.patch.object(self.row._dialog, "open_finish") as mock_finish: task = Gio.Task() signal = unittest.mock.Mock() mock_finish.return_value = Gio.File.new_for_path("/a/b/c/1.ogg") - self.button.connect("track-requested", signal) + self.row.connect("track-requested", signal) - self.button._Button__async_ready(self.button._dialog, task) + self.row._OpenRow__async_ready(self.row._dialog, task) mock_finish.assert_called_with(task) - signal.assert_called_with(self.button, - pathlib.Path("/a/b/c/1.ogg")) + signal.assert_called_with(self.row, pathlib.Path("/a/b/c/1.ogg"))