Compare commits

..

No commits in common. "3f153e1423ddfa401bdca17fd97feba135400c2a" and "bee48deac6b57342c8cdee9005c90e51d645545b" have entirely different histories.

11 changed files with 55 additions and 261 deletions

View File

@ -206,14 +206,12 @@ class Application(Adw.Application):
now_playing=self.build_now_playing(),
sidebar=self.build_sidebar(),
tracklist=self.build_tracklist())
win.bind_property("show-sidebar", win.header, "show-sidebar",
GObject.BindingFlags.BIDIRECTIONAL)
win.bind_property("user-editing", win.now_playing, "editing")
for (setting, property) in [("window.width", "default-width"),
("window.height", "default-height"),
("now-playing.size", "now-playing-size"),
("sidebar.show", "show-sidebar")]:
("sidebar.size", "sidebar-size")]:
self.db.settings.bind_setting(setting, win, property)
self.__add_accelerators(win.accelerators)

View File

@ -34,7 +34,6 @@ class Header(Gtk.HeaderBar):
sql = GObject.Property(type=db.Connection)
title = GObject.Property(type=str)
subtitle = GObject.Property(type=str)
show_sidebar = GObject.Property(type=bool, default=False)
bg_enabled = GObject.Property(type=bool, default=False)
bg_volume = GObject.Property(type=float, default=0.5)
rg_enabled = GObject.Property(type=bool, default=False)
@ -44,8 +43,6 @@ class Header(Gtk.HeaderBar):
def __init__(self, sql: db.Connection, title: str):
"""Initialize the HeaderBar."""
super().__init__(title=title, subtitle=SUBTITLE, sql=sql)
icon = "sidebar-show-symbolic"
self._show_sidebar = Gtk.ToggleButton(icon_name=icon, has_frame=False)
self._open = open.Button()
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle,
tooltip_text=gsetup.env_string())
@ -71,8 +68,6 @@ class Header(Gtk.HeaderBar):
self.bind_property("title", self._title, "title")
self.bind_property("subtitle", self._title, "subtitle")
self.bind_property("show-sidebar", self._show_sidebar, "active",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("bg-enabled", self._background, "enabled",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("bg-volume", self._background, "volume",
@ -84,7 +79,6 @@ class Header(Gtk.HeaderBar):
self.bind_property("volume", self._volume, "volume",
GObject.BindingFlags.BIDIRECTIONAL)
self.pack_start(self._show_sidebar)
self.pack_start(self._open)
if __debug__:
self._window = settings.Window(sql)
@ -134,9 +128,7 @@ class Header(Gtk.HeaderBar):
ActionEntry("increase-volume", self._volume.increment,
"<Shift><Control>Up"),
ActionEntry("toggle-bg-mode", self._background.activate,
"<Shift><Control>b"),
ActionEntry("toggle-sidebar", self._show_sidebar.activate,
"<Control>bracketright")]
"<Shift><Control>b")]
if __debug__:
res.append(ActionEntry("edit-settings", self._settings.activate,
"<Shift><Control>s"))

View File

@ -1,61 +0,0 @@
# Copyright 2023 (c) Anna Schumaker.
"""Our adaptable layout that can rearrange widgets as the window is resized."""
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Adw
MIN_WIDTH = Adw.BreakpointConditionLengthType.MIN_WIDTH
class Layout(Adw.Bin):
"""A widget that can rearrange based on window dimensions."""
show_sidebar = GObject.Property(type=bool, default=False)
wide_view = GObject.Property(type=bool, default=False)
def __init__(self, *, content: Gtk.Widget = None,
sidebar: Gtk.Widget = None):
"""Initialize our Layout widget."""
super().__init__()
self._split_view = Adw.OverlaySplitView(content=content,
sidebar=sidebar,
collapsed=not self.wide_view)
self.props.child = self._split_view
self.bind_property("show-sidebar", self._split_view, "show-sidebar",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("wide-view", self._split_view, "collapsed",
GObject.BindingFlags.INVERT_BOOLEAN)
def __define_breakpoint(self, property: str, value: bool,
length: int) -> Adw.Breakpoint:
condition = Adw.BreakpointCondition.new_length(MIN_WIDTH, length,
Adw.LengthUnit.SP)
breakpoint = Adw.Breakpoint.new(condition)
breakpoint.add_setter(self, property, GObject.Value(bool, value))
return breakpoint
@GObject.Property(type=Gtk.Widget)
def content(self) -> Gtk.Widget:
"""Get the content widget for the Layout."""
return self._split_view.props.content
@content.setter
def content(self, widget: Gtk.Widget) -> None:
self._split_view.props.content = widget
@GObject.Property(type=Gtk.Widget)
def sidebar(self) -> Gtk.Widget:
"""Get the sidebar widget for the Layout."""
return self._split_view.props.sidebar
@sidebar.setter
def sidebar(self, widget: Gtk.Widget) -> None:
self._split_view.props.sidebar = widget
@property
def breakpoints(self) -> list[Adw.Breakpoint]:
"""Get a list of breakpoints supported by the layout."""
return [self.__define_breakpoint("wide-view", True, 1000)]

View File

@ -1,7 +1,6 @@
# Copyright 2022 (c) Anna Schumaker.
"""A Footer widget to display below the TrackView."""
from gi.repository import GObject
from gi.repository import Pango
from gi.repository import Gtk
@ -15,11 +14,9 @@ class Footer(Gtk.CenterBox):
def __init__(self, **kwargs):
"""Initialize a Footer widget."""
super().__init__(**kwargs)
self._count = Gtk.Label(label="Showing 0 tracks", xalign=0.0,
ellipsize=Pango.EllipsizeMode.START)
self._selected = Gtk.Label(ellipsize=Pango.EllipsizeMode.MIDDLE)
self._runtime = Gtk.Label(label="0 seconds", xalign=1.0,
ellipsize=Pango.EllipsizeMode.END)
self._count = Gtk.Label(label="Showing 0 tracks", xalign=0.0)
self._selected = Gtk.Label()
self._runtime = Gtk.Label(label="0 seconds", xalign=1.0)
self.set_start_widget(self._count)
self.set_center_widget(self._selected)

View File

@ -4,7 +4,6 @@ from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Adw
from .action import ActionEntry
from . import layout
def _make_pane(orientation: Gtk.Orientation, position: int = 0,
@ -13,7 +12,7 @@ def _make_pane(orientation: Gtk.Orientation, position: int = 0,
pane = Gtk.Paned(orientation=orientation, hexpand=True, vexpand=True,
shrink_start_child=False, resize_start_child=False,
start_child=start_child, end_child=end_child,
position=position, margin_start=8)
position=position)
pane.add_css_class("emmental-pane")
return pane
@ -30,7 +29,7 @@ class Window(Adw.Window):
header = GObject.Property(type=Gtk.Widget)
sidebar = GObject.Property(type=Gtk.Widget)
show_sidebar = GObject.Property(type=bool, default=False)
sidebar_size = GObject.Property(type=int, default=300)
now_playing = GObject.Property(type=Gtk.Widget)
now_playing_size = GObject.Property(type=int, default=250)
tracklist = GObject.Property(type=Gtk.Widget)
@ -39,25 +38,26 @@ class Window(Adw.Window):
def __init__(self, version: str, **kwargs):
"""Initialize our Window."""
super().__init__(icon_name="emmental", title=version,
default_width=1600, default_height=900,
width_request=525, height_request=500, **kwargs)
default_width=1600, default_height=900, **kwargs)
self._box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
self._header = Adw.Bin(child=self.header)
self._inner_pane = _make_pane(Gtk.Orientation.VERTICAL,
position=self.now_playing_size,
start_child=self.now_playing,
end_child=self.tracklist)
self._layout = layout.Layout(content=self._inner_pane,
sidebar=self.sidebar)
self._toast = Adw.ToastOverlay(child=self._layout)
self._outer_pane = _make_pane(Gtk.Orientation.HORIZONTAL,
position=self.sidebar_size,
start_child=self.sidebar,
end_child=self._inner_pane)
self._toast = Adw.ToastOverlay(child=self._outer_pane)
self._layout.add_css_class("emmental-padding")
self._outer_pane.add_css_class("emmental-padding")
if __debug__:
self.add_css_class("devel")
self.bind_property("header", self._header, "child")
self.bind_property("sidebar", self._layout, "sidebar")
self.bind_property("show-sidebar", self._layout, "show-sidebar",
self.bind_property("sidebar", self._outer_pane, "start-child")
self.bind_property("sidebar-size", self._outer_pane, "position",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("now-playing", self._inner_pane, "start-child")
self.bind_property("now-playing-size", self._inner_pane, "position",
@ -66,9 +66,6 @@ class Window(Adw.Window):
self.connect("notify::focus-widget", self.__notify_focus_widget)
for breakpoint in self._layout.breakpoints:
self.add_breakpoint(breakpoint)
self._box.append(self._header)
self._box.append(self._toast)
self.set_content(self._box)

View File

@ -36,21 +36,6 @@ class TestHeader(tests.util.TestCase):
self.assertEqual(self.header._title.get_tooltip_text(),
emmental.gsetup.env_string())
def test_show_sidebar(self):
"""Check that the show sidebar button works as expected."""
self.assertIsInstance(self.header._show_sidebar, Gtk.ToggleButton)
self.assertEqual(self.header._show_sidebar.props.icon_name,
"sidebar-show-symbolic")
self.assertFalse(self.header._show_sidebar.props.has_frame)
self.assertFalse(self.header._show_sidebar.props.active)
self.assertFalse(self.header.show_sidebar)
self.header.show_sidebar = True
self.assertTrue(self.header._show_sidebar.props.active)
self.header._show_sidebar.props.active = False
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)
@ -205,8 +190,6 @@ class TestHeader(tests.util.TestCase):
"<Shift><Control>Up"),
("toggle-bg-mode", self.header._background.activate,
"<Shift><Control>b"),
("toggle-sidebar", self.header._show_sidebar.activate,
"<Control>bracketright"),
("edit-settings", self.header._settings.activate,
"<Shift><Control>s")]

View File

@ -131,7 +131,6 @@ class TestEmmental(unittest.TestCase):
("app.decrease-volume", "<Shift><Control>Down"),
("app.increase-volume", "<Shift><Control>Up"),
("app.toggle-bg-mode", "<Shift><Control>b"),
("app.toggle-sidebar", "<Control>bracketright"),
("app.edit-settings", "<Shift><Control>s")]:
self.assertEqual(self.application.get_accels_for_action(action),
[accel])
@ -293,17 +292,3 @@ class TestEmmental(unittest.TestCase):
win.header.bg_volume = 0.5
self.assertTrue(player.bg_enabled)
self.assertEqual(player.bg_volume, 0.5)
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_show_sidebar(self, mock_stdout: io.StringIO):
"""Test showing the sidebar."""
self.application.db = emmental.db.Connection()
self.application.factory = emmental.playlist.Factory(
self.application.db)
self.application.player = emmental.audio.Player()
win = self.application.build_window()
win.show_sidebar = True
self.assertTrue(win.header.show_sidebar)
win.header.show_sidebar = False
self.assertFalse(win.show_sidebar)

View File

@ -1,87 +0,0 @@
# Copyright 2023 (c) Anna Schumaker.
"""Tests our adaptable layout widget."""
import unittest
import emmental.layout
from gi.repository import Gtk
from gi.repository import Adw
class TestLayout(unittest.TestCase):
"""Test case for our adaptable layout."""
def setUp(self):
"""Set up common variables."""
self.layout = emmental.layout.Layout()
def test_constants(self):
"""Check constant variables."""
self.assertEqual(emmental.layout.MIN_WIDTH,
Adw.BreakpointConditionLengthType.MIN_WIDTH)
def test_init(self):
"""Check that the layout is set up properly."""
self.assertIsInstance(self.layout, Adw.Bin)
self.assertIsInstance(self.layout._split_view, Adw.OverlaySplitView)
self.assertTrue(self.layout._split_view.props.collapsed)
def test_wide_view(self):
"""Test the layout when we have a wide window."""
self.assertFalse(self.layout.wide_view)
self.assertEqual(self.layout.props.child, self.layout._split_view)
self.layout.wide_view = True
self.assertFalse(self.layout._split_view.props.collapsed)
def test_content(self):
"""Test the content widget property."""
self.assertIsNone(self.layout.content)
widget = Gtk.Label()
self.layout.content = widget
self.assertEqual(self.layout._split_view.props.content, widget)
self.assertEqual(self.layout.content, widget)
widget2 = Gtk.Label()
layout2 = emmental.layout.Layout(content=widget2)
self.assertEqual(layout2.content, widget2)
def test_sidebar(self):
"""Test the sidebar widget property."""
self.assertIsNone(self.layout.sidebar)
widget = Gtk.Label()
self.layout.sidebar = widget
self.assertEqual(self.layout._split_view.props.sidebar, widget)
self.assertEqual(self.layout.sidebar, widget)
widget2 = Gtk.Label()
layout2 = emmental.layout.Layout(sidebar=widget2)
self.assertEqual(layout2.sidebar, widget2)
def test_show_sidebar(self):
"""Test the show-sidebar property."""
self.assertFalse(self.layout.show_sidebar)
self.assertFalse(self.layout._split_view.props.show_sidebar)
self.layout.show_sidebar = True
self.assertTrue(self.layout._split_view.props.show_sidebar)
self.layout._split_view.props.show_sidebar = False
self.assertFalse(self.layout.show_sidebar)
@unittest.mock.patch("gi.repository.Adw.Breakpoint.add_setter")
def test_breakpoints(self, mock_add_setter: unittest.mock.Mock):
"""Test the layout breakpoints property."""
points = self.layout.breakpoints
self.assertEqual(len(points), 1)
self.assertIsInstance(points[0], Adw.Breakpoint)
condition = points[0].props.condition
self.assertIsInstance(condition, Adw.BreakpointCondition)
self.assertEqual(condition.to_string(), "min-width: 1000sp")
mock_add_setter.assert_called_once()
args = mock_add_setter.mock_calls[0].args
self.assertEqual(args[0], self.layout)
self.assertEqual(args[1], "wide-view")
self.assertTrue(args[2].get_boolean())

View File

@ -38,16 +38,6 @@ class TestSettings(unittest.TestCase):
win = self.app.build_window()
self.assertEqual(win.get_default_size(), (100, 200))
def test_save_show_sidebar(self, mock_stdout: io.StringIO):
"""Check saving and loading the show-sidebar property."""
self.assertFalse(self.settings["sidebar.show"])
self.win.show_sidebar = True
self.assertTrue(self.settings["sidebar.show"])
win = self.app.build_window()
self.assertTrue(win.show_sidebar)
def test_save_volume(self, mock_stdout: io.StringIO):
"""Check saving and loading volume from the database."""
self.assertEqual(self.settings["audio.volume"], 1.0)
@ -121,6 +111,16 @@ class TestSettings(unittest.TestCase):
self.assertFalse(self.app.build_window().now_playing.prefer_artist)
def test_save_sidebar_size(self, mock_stdout: io.StringIO):
"""Check saving and loading the sidebar widget size."""
self.assertEqual(self.win.sidebar_size, 300)
self.assertEqual(self.settings["sidebar.size"], 300)
self.win.sidebar_size = 400
self.assertEqual(self.settings["sidebar.size"], 400)
self.assertEqual(self.app.build_window().sidebar_size, 400)
def test_save_sidebar_show_all_artists(self, mock_stdout: io.StringIO):
"""Check saving and loading the show-all artists setting."""
self.assertFalse(self.win.sidebar.show_all_artists)

View File

@ -22,8 +22,8 @@ class TestWindow(unittest.TestCase):
self.assertIsInstance(self.window, Adw.Window)
self.assertIsInstance(self.window._box, Gtk.Box)
self.assertIsInstance(self.window._header, Adw.Bin)
self.assertIsInstance(self.window._outer_pane, Gtk.Paned)
self.assertIsInstance(self.window._inner_pane, Gtk.Paned)
self.assertIsInstance(self.window._layout, emmental.layout.Layout)
self.assertIsInstance(self.window._toast, Adw.ToastOverlay)
self.assertTrue(self.window.has_css_class("devel"))
@ -31,9 +31,6 @@ class TestWindow(unittest.TestCase):
self.assertEqual(self.window.get_title(), "Test 1.2.3")
self.assertEqual(self.window.get_default_size(), (1600, 900))
self.assertEqual(self.window.props.width_request, 525)
self.assertEqual(self.window.props.height_request, 500)
def test_content(self):
"""Check that the Window content is set up properly."""
self.assertEqual(self.window._box.get_orientation(),
@ -44,19 +41,22 @@ class TestWindow(unittest.TestCase):
self.window._header)
self.assertEqual(self.window._header.get_next_sibling(),
self.window._toast)
self.assertEqual(self.window._toast.get_child(), self.window._layout)
self.assertEqual(self.window._layout.content, self.window._inner_pane)
self.assertTrue(self.window._layout.has_css_class(
self.assertEqual(self.window._toast.get_child(),
self.window._outer_pane)
self.assertEqual(self.window._outer_pane.get_end_child(),
self.window._inner_pane)
self.assertTrue(self.window._outer_pane.has_css_class(
"emmental-padding"))
self.assertEqual(self.window._inner_pane.get_orientation(),
Gtk.Orientation.VERTICAL)
self.assertEqual(self.window._inner_pane.get_margin_start(), 8)
self.assertFalse(self.window._inner_pane.get_shrink_start_child())
self.assertFalse(self.window._inner_pane.get_resize_start_child())
self.assertTrue(self.window._inner_pane.get_hexpand())
self.assertTrue(self.window._inner_pane.get_vexpand())
self.assertTrue(self.window._inner_pane.has_css_class("emmental-pane"))
subtests = [(self.window._outer_pane, Gtk.Orientation.HORIZONTAL),
(self.window._inner_pane, Gtk.Orientation.VERTICAL)]
for pane, orientation in subtests:
self.assertEqual(pane.get_orientation(), orientation)
self.assertFalse(pane.get_shrink_start_child())
self.assertFalse(pane.get_resize_start_child())
self.assertTrue(pane.get_hexpand())
self.assertTrue(pane.get_vexpand())
self.assertTrue(pane.has_css_class("emmental-pane"))
def test_header(self):
"""Check setting a widget to the header area."""
@ -72,22 +72,26 @@ class TestWindow(unittest.TestCase):
"""Check setting a widget to the sidebar area."""
self.assertIsNone(self.window.sidebar)
self.window.sidebar = Gtk.Label()
self.assertEqual(self.window._layout.sidebar, self.window.sidebar)
self.assertEqual(self.window._outer_pane.get_start_child(),
self.window.sidebar)
window2 = emmental.window.Window(version="1.2.3", sidebar=Gtk.Label())
self.assertIsInstance(window2.sidebar, Gtk.Label)
self.assertEqual(window2._layout.sidebar, window2.sidebar)
self.assertEqual(window2._outer_pane.get_start_child(),
window2.sidebar)
def test_show_sidebar(self):
"""Check setting the show-sidebar property."""
self.assertFalse(self.window.show_sidebar)
self.assertFalse(self.window._layout.show_sidebar)
def test_sidebar_size(self):
"""Check setting the size of the sidebar area."""
self.assertEqual(self.window.sidebar_size, 300)
self.assertEqual(self.window._outer_pane.get_position(), 300)
self.window.show_sidebar = True
self.assertTrue(self.window._layout.show_sidebar)
self.window.sidebar_size = 100
self.assertEqual(self.window.sidebar_size, 100)
self.assertEqual(self.window._outer_pane.get_position(), 100)
self.window._layout.show_sidebar = False
self.assertFalse(self.window.show_sidebar)
self.window._outer_pane.set_position(200)
self.assertEqual(self.window.sidebar_size, 200)
self.assertEqual(self.window._outer_pane.get_position(), 200)
def test_now_playing(self):
"""Check setting a widget to the now_playing area."""
@ -166,10 +170,3 @@ class TestWindow(unittest.TestCase):
self.assertEqual(accels[0].name, "reset-focus")
self.assertEqual(accels[0].func, self.window.set_focus)
self.assertListEqual(accels[0].accels, ["Escape"])
@unittest.mock.patch("emmental.window.Window.add_breakpoint")
def test_breakpoints(self, mock_add_breakpoint: unittest.mock.Mock):
"""Test that the Window breakpoints are set up properly."""
window2 = emmental.window.Window(version="1.2.3")
self.assertEqual(len(mock_add_breakpoint.mock_calls),
len(window2._layout.breakpoints))

View File

@ -2,7 +2,6 @@
"""Tests our Tracklist Footer."""
import unittest
import emmental.tracklist.footer
from gi.repository import Pango
from gi.repository import Gtk
@ -23,8 +22,6 @@ class TestFooter(unittest.TestCase):
self.assertIsInstance(self.footer._count, Gtk.Label)
self.assertEqual(self.footer._count.get_xalign(), 0.0)
self.assertEqual(self.footer.get_start_widget(), self.footer._count)
self.assertEqual(self.footer._count.get_ellipsize(),
Pango.EllipsizeMode.START)
self.assertEqual(self.footer.count, 0)
self.assertEqual(self.footer._count.get_text(), "Showing 0 tracks")
@ -39,8 +36,6 @@ class TestFooter(unittest.TestCase):
self.assertEqual(self.footer._selected.get_xalign(), 0.5)
self.assertEqual(self.footer.get_center_widget(),
self.footer._selected)
self.assertEqual(self.footer._selected.get_ellipsize(),
Pango.EllipsizeMode.MIDDLE)
self.assertEqual(self.footer.selected, 0)
self.assertEqual(self.footer._selected.get_text(), "")
@ -56,8 +51,6 @@ class TestFooter(unittest.TestCase):
self.assertIsInstance(self.footer._runtime, Gtk.Label)
self.assertEqual(self.footer._runtime.get_xalign(), 1.0)
self.assertEqual(self.footer.get_end_widget(), self.footer._runtime)
self.assertEqual(self.footer._runtime.get_ellipsize(),
Pango.EllipsizeMode.END)
self.assertEqual(self.footer.runtime, 0.0)
self.assertEqual(self.footer._runtime.get_text(),