header/open: Create an Open button

This button will be used by the user to open and play a track from the
filesystem.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-03-13 14:51:43 -04:00
parent bb1d01f951
commit 5d0d522e3c
4 changed files with 119 additions and 0 deletions

View File

@ -1,10 +1,12 @@
# Copyright 2022 (c) Anna Schumaker.
"""A custom Gtk.HeaderBar configured for our application."""
import pathlib
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Adw
from .. import db
from .. import buttons
from . import open
from . import replaygain
from . import volume
if __debug__:
@ -36,6 +38,7 @@ class Header(Gtk.HeaderBar):
def __init__(self, sql: db.Connection, title: str):
"""Initialize the HeaderBar."""
super().__init__(title=title, subtitle=SUBTITLE, sql=sql)
self._open = open.Button()
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle)
self._volume = volume.Controls()
self._replaygain = replaygain.Selector()
@ -58,6 +61,7 @@ class Header(Gtk.HeaderBar):
self.bind_property("volume", self._volume, "volume",
GObject.BindingFlags.BIDIRECTIONAL)
self.pack_start(self._open)
if __debug__:
self._window = settings.Window(sql)
self._settings = Gtk.Button.new_from_icon_name("settings-symbolic")
@ -66,6 +70,8 @@ class Header(Gtk.HeaderBar):
self.pack_end(self._button)
self.set_title_widget(self._title)
self._open.connect("track-requested", self.__track_requested)
self.connect("notify::volume", self.__notify_volume)
def __run_settings(self, button: Gtk.Button) -> None:
@ -74,3 +80,11 @@ class Header(Gtk.HeaderBar):
def __notify_volume(self, header, param) -> None:
self._button.set_icon_name(_volume_icon(self.volume))
def __track_requested(self, button: open.Button,
path: pathlib.Path) -> None:
self.emit("track-requested", path)
@GObject.Signal(arg_types=(GObject.TYPE_PYOBJECT,))
def track_requested(self, path: pathlib.Path) -> None:
"""Signal that a track has been requested."""

39
emmental/header/open.py Normal file
View File

@ -0,0 +1,39 @@
# Copyright 2023 (c) Anna Schumaker.
"""A custom Button that opens a FileDialog 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
class Button(Gtk.Button):
"""Our pre-configured open button."""
def __init__(self):
"""Initialize our open button."""
super().__init__(icon_name="document-open-symbolic")
self._filters = Gio.ListStore()
self._filter = Gtk.FileFilter(name="Audio Files",
mime_types=["inode/directory",
"audio/*"])
self._dialog = Gtk.FileDialog(filters=self._filters,
title="Pick a Track")
self._filters.append(self._filter)
def __async_ready(self, dialog: Gtk.FileDialog, task: Gio.Task) -> None:
try:
file = dialog.open_finish(task)
self.emit("track-requested", pathlib.Path(file.get_path()))
except GLib.Error:
pass
def do_clicked(self) -> None:
"""Handle a click event."""
self._dialog.open(self.get_ancestor(Gtk.Window), None,
self.__async_ready)
@GObject.Signal(arg_types=(GObject.TYPE_PYOBJECT,))
def track_requested(self, file: pathlib.Path) -> None:
"""Signal that a track has been requested."""

View File

@ -2,6 +2,7 @@
"""Tests our application header."""
import emmental
import tests.util
import pathlib
import unittest.mock
from gi.repository import Gtk
from gi.repository import Adw
@ -32,6 +33,15 @@ class TestHeader(tests.util.TestCase):
self.assertEqual(self.header._title.get_subtitle(),
emmental.header.SUBTITLE)
def test_open(self):
"""Check that the Open button works as expected."""
self.assertIsInstance(self.header._open, emmental.header.open.Button)
signal = unittest.mock.Mock()
self.header.connect("track-requested", signal)
self.header._open.emit("track-requested", pathlib.Path("/a/b/c/1.ogg"))
signal.assert_called_with(self.header, pathlib.Path("/a/b/c/1.ogg"))
def test_settings(self):
"""Check that the Settings window is set up correctly."""
self.assertIsInstance(self.header._settings, Gtk.Button)

56
tests/header/test_open.py Normal file
View File

@ -0,0 +1,56 @@
# Copyright 2023 (c) Anna Schumaker.
"""Tests our Open button."""
import emmental.header.open
import pathlib
import unittest
from gi.repository import Gio
from gi.repository import Gtk
class TestButton(unittest.TestCase):
"""Test the Open button."""
def setUp(self):
"""Set up common variables."""
self.button = emmental.header.open.Button()
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")
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.assertEqual(self.button._filter.get_name(), "Audio Files")
self.assertEqual(self.button._filters[0], self.button._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())
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)
with unittest.mock.patch.object(self.button._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.button._Button__async_ready(self.button._dialog, task)
mock_finish.assert_called_with(task)
signal.assert_called_with(self.button,
pathlib.Path("/a/b/c/1.ogg"))