header/dialog: Create a Settings Dialog
This dialog is used to manually edit the settings in the database. I bind the properties in such a way that changes are seen instantly. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
50270bd04c
commit
4cd1e89493
|
@ -4,6 +4,8 @@ from gi.repository import GObject
|
|||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
from .. import db
|
||||
if __debug__:
|
||||
from . import settings
|
||||
|
||||
SUBTITLE = "The Cheesy Music Player"
|
||||
|
||||
|
@ -23,4 +25,14 @@ class Header(Gtk.HeaderBar):
|
|||
self.bind_property("title", self._title, "title")
|
||||
self.bind_property("subtitle", self._title, "subtitle")
|
||||
|
||||
if __debug__:
|
||||
self._window = settings.Window(sql)
|
||||
self._settings = Gtk.Button.new_from_icon_name("settings-symbolic")
|
||||
self._settings.connect("clicked", self.__run_settings)
|
||||
self.pack_start(self._settings)
|
||||
|
||||
self.set_title_widget(self._title)
|
||||
|
||||
def __run_settings(self, button: Gtk.Button) -> None:
|
||||
if __debug__:
|
||||
self._window.present()
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""A custom Gtk.Dialog for showing Settings."""
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
from .. import db
|
||||
from .. import entry
|
||||
from .. import factory
|
||||
|
||||
|
||||
class ValueRow(factory.ListRow):
|
||||
"""A Row for displaying settings values."""
|
||||
|
||||
def do_bind(self) -> None:
|
||||
"""Bind a db.Setting to this Row."""
|
||||
if isinstance(self.item.value, bool):
|
||||
self.child = Gtk.Switch(halign=Gtk.Align.START)
|
||||
self.bind_and_set_property("value", "active", bidirectional=True)
|
||||
elif isinstance(self.item.value, str):
|
||||
self.child = entry.String(has_frame=False)
|
||||
self.bind_and_set_property("value", "value", bidirectional=True)
|
||||
elif isinstance(self.item.value, int):
|
||||
self.child = entry.Integer(has_frame=False)
|
||||
self.bind_and_set_property("value", "value", bidirectional=True)
|
||||
elif isinstance(self.item.value, float):
|
||||
self.child = entry.Float(has_frame=False)
|
||||
self.bind_and_set_property("value", "value", bidirectional=True)
|
||||
|
||||
|
||||
class Window(Adw.Window):
|
||||
"""A custom window that displays the current settings."""
|
||||
|
||||
def __init__(self, sql: db.Connection):
|
||||
"""Initialize the Settings window."""
|
||||
super().__init__(default_width=500, default_height=500,
|
||||
title="Emmental Settings", icon_name="settings",
|
||||
hide_on_close=True,
|
||||
content=Gtk.Box.new(Gtk.Orientation.VERTICAL, 0))
|
||||
self._search = entry.Filter(what="settings")
|
||||
self._header = Gtk.HeaderBar(title_widget=self._search)
|
||||
self._selection = Gtk.NoSelection(model=sql.settings)
|
||||
self._view = Gtk.ColumnView(model=self._selection,
|
||||
show_row_separators=True)
|
||||
self._scroll = Gtk.ScrolledWindow(child=self._view, vexpand=True)
|
||||
|
||||
self._scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
|
||||
self.__append_column(factory.InscriptionFactory("key"),
|
||||
"Key", width=400)
|
||||
self.__append_column(factory.Factory(row_type=ValueRow),
|
||||
"Value", width=100)
|
||||
|
||||
self.get_content().append(self._header)
|
||||
self.get_content().append(self._scroll)
|
||||
|
||||
if __debug__:
|
||||
self.add_css_class("devel")
|
||||
self._search.connect("search-changed", self.__filter)
|
||||
|
||||
def __append_column(self, factory: factory.Factory,
|
||||
title: str, *, width: int) -> None:
|
||||
self._view.append_column(Gtk.ColumnViewColumn(factory=factory,
|
||||
title=title,
|
||||
fixed_width=width))
|
||||
|
||||
def __filter(self, entry: entry.Filter) -> None:
|
||||
self._selection.get_model().filter(entry.get_query())
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.5 1.019531 c -0.550781 0 -0.996094 0.445313 -0.996094 0.996094 v 0.453125 c -0.472656 0.128906 -0.929687 0.320312 -1.355468 0.566406 l -0.324219 -0.324218 c -0.390625 -0.390626 -1.019531 -0.390626 -1.410157 0 l -0.703124 0.707031 c -0.390626 0.390625 -0.390626 1.019531 0 1.410156 l 0.320312 0.320313 c -0.246094 0.425781 -0.433594 0.882812 -0.5625 1.355468 h -0.453125 c -0.550781 0 -0.996094 0.445313 -0.996094 0.996094 v 1 c 0 0.550781 0.445313 0.996094 0.996094 0.996094 h 0.449219 c 0.132812 0.472656 0.320312 0.929687 0.566406 1.355468 l -0.320312 0.320313 c -0.390626 0.390625 -0.390626 1.019531 0 1.410156 l 0.703124 0.707031 c 0.390626 0.390626 1.019532 0.390626 1.410157 0 l 0.320312 -0.320312 c 0.429688 0.242188 0.882813 0.433594 1.359375 0.558594 v 0.457031 c 0 0.550781 0.445313 0.996094 0.996094 0.996094 h 0.996094 c 0.554687 0 1 -0.445313 1 -0.996094 v -0.453125 c 0.472656 -0.128906 0.929687 -0.320312 1.355468 -0.566406 l 0.320313 0.324218 c 0.390625 0.390626 1.019531 0.390626 1.410156 0 l 0.707031 -0.707031 c 0.390626 -0.390625 0.390626 -1.019531 0 -1.410156 l -0.320312 -0.320313 c 0.242188 -0.425781 0.433594 -0.882812 0.558594 -1.355468 h 0.453125 c 0.554687 0 1 -0.445313 1 -0.996094 v -1 c 0 -0.550781 -0.445313 -0.996094 -1 -0.996094 h -0.449219 c -0.128906 -0.472656 -0.320312 -0.929687 -0.566406 -1.355468 l 0.324218 -0.320313 c 0.390626 -0.390625 0.390626 -1.019531 0 -1.410156 l -0.707031 -0.707031 c -0.390625 -0.390626 -1.019531 -0.390626 -1.410156 0 l -0.320313 0.320312 c -0.425781 -0.242188 -0.882812 -0.429688 -1.355468 -0.558594 v -0.457031 c 0 -0.550781 -0.445313 -0.996094 -1 -0.996094 z m 0.515625 3.976563 c 1.660156 0 3 1.34375 3 3 s -1.339844 3 -3 3 c -1.65625 0 -3 -1.34375 -3 -3 s 1.34375 -3 3 -3 z m 0 0" fill="#222222"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,2 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Needed to fix the unique basename problem with pytest."""
|
|
@ -2,6 +2,7 @@
|
|||
"""Tests our application header."""
|
||||
import emmental
|
||||
import tests.util
|
||||
import unittest.mock
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
@ -30,3 +31,18 @@ class TestHeader(tests.util.TestCase):
|
|||
self.assertEqual(self.header.subtitle, emmental.header.SUBTITLE)
|
||||
self.assertEqual(self.header._title.get_subtitle(),
|
||||
emmental.header.SUBTITLE)
|
||||
|
||||
def test_settings(self):
|
||||
"""Check that the Settings window is set up correctly."""
|
||||
self.assertIsInstance(self.header._settings, Gtk.Button)
|
||||
self.assertIsInstance(self.header._window,
|
||||
emmental.header.settings.Window)
|
||||
|
||||
self.assertEqual(self.header.sql, self.sql)
|
||||
self.assertEqual(self.header._settings.get_icon_name(),
|
||||
"settings-symbolic")
|
||||
|
||||
with unittest.mock.patch.object(self.header._window,
|
||||
"present") as mock_present:
|
||||
self.header._settings.emit("clicked")
|
||||
mock_present.assert_called()
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""Tests our Settings dialog."""
|
||||
import unittest.mock
|
||||
import emmental.header.settings
|
||||
import emmental.entry
|
||||
import tests.util
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestValueRow(tests.util.TestCase):
|
||||
"""Test the settings Value Row."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
super().setUp()
|
||||
self.listitem = Gtk.ListItem()
|
||||
self.row = emmental.header.settings.ValueRow(self.listitem)
|
||||
|
||||
def make_setting(self, name: str, type: str, value: any) -> None:
|
||||
"""Create a setting in the database."""
|
||||
setting = self.sql.settings.create(name, type, value)
|
||||
self.listitem.get_item = unittest.mock.Mock(return_value=setting)
|
||||
|
||||
def test_init(self):
|
||||
"""Tetst that the ValueRow is configured correctly."""
|
||||
self.assertIsInstance(self.row, emmental.factory.ListRow)
|
||||
self.assertIsNone(self.row.child)
|
||||
|
||||
def test_boolean_setting(self):
|
||||
"""Test configuring the row for a boolean setting."""
|
||||
self.make_setting("test.bool", "gboolean", True)
|
||||
|
||||
self.row.bind()
|
||||
self.assertIsInstance(self.row.child, Gtk.Switch)
|
||||
self.assertEqual(self.row.child.get_halign(), Gtk.Align.START)
|
||||
self.assertTrue(self.row.child.get_active())
|
||||
|
||||
def test_string_setting(self):
|
||||
"""Test configuring the row for a string setting."""
|
||||
self.make_setting("test.string", "gchararray", "Test Text")
|
||||
|
||||
self.row.bind()
|
||||
self.assertIsInstance(self.row.child, emmental.entry.String)
|
||||
self.assertFalse(self.row.child.get_has_frame())
|
||||
self.assertEqual(self.row.child.value, "Test Text")
|
||||
|
||||
def test_integer_setting(self):
|
||||
"""Test configuring the row for an integer setting."""
|
||||
self.make_setting("test.int", "gint", 42)
|
||||
|
||||
self.row.bind()
|
||||
self.assertIsInstance(self.row.child, emmental.entry.Integer)
|
||||
self.assertFalse(self.row.child.get_has_frame())
|
||||
self.assertEqual(self.row.child.value, 42)
|
||||
|
||||
def test_float_setting(self):
|
||||
"""Test configuring the row for a float setting."""
|
||||
self.make_setting("test.float", "gdouble", 1.234)
|
||||
|
||||
self.row.bind()
|
||||
self.assertIsInstance(self.row.child, emmental.entry.Float)
|
||||
self.assertFalse(self.row.child.get_has_frame())
|
||||
self.assertEqual(self.row.child.value, 1.234)
|
||||
|
||||
|
||||
class TestWindow(tests.util.TestCase):
|
||||
"""Test the Settings Window."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
super().setUp()
|
||||
self.window = emmental.header.settings.Window(sql=self.sql)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the Settings Window is configured properly."""
|
||||
self.assertIsInstance(self.window, Adw.Window)
|
||||
self.assertEqual(self.window.get_default_size(), (500, 500))
|
||||
self.assertEqual(self.window.get_title(), "Emmental Settings")
|
||||
self.assertEqual(self.window.get_icon_name(), "settings")
|
||||
self.assertTrue(self.window.get_hide_on_close())
|
||||
self.assertTrue(self.window.has_css_class("devel"))
|
||||
|
||||
def test_content(self):
|
||||
"""Test that the Window content was set up properly."""
|
||||
box = self.window.get_content()
|
||||
self.assertIsInstance(box, Gtk.Box)
|
||||
self.assertEqual(box.get_orientation(), Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(box.get_spacing(), 0)
|
||||
|
||||
def test_headerbar(self):
|
||||
"""Test that the Window headerbar was set up properly."""
|
||||
self.assertIsInstance(self.window._header, Gtk.HeaderBar)
|
||||
self.assertEqual(self.window.get_content().get_first_child(),
|
||||
self.window._header)
|
||||
|
||||
def test_search(self):
|
||||
"""Test the search entry widget."""
|
||||
self.assertIsInstance(self.window._search, emmental.entry.Filter)
|
||||
self.assertEqual(self.window._search.get_placeholder_text(),
|
||||
"type to filter settings")
|
||||
self.assertEqual(self.window._header.get_title_widget(),
|
||||
self.window._search)
|
||||
|
||||
self.window._search.set_text("abcde")
|
||||
with unittest.mock.patch.object(self.sql.settings,
|
||||
"filter") as mock_filter:
|
||||
self.window._search.emit("search-changed")
|
||||
mock_filter.assert_called_with("*abcde*")
|
||||
|
||||
def test_columnview(self):
|
||||
"""Test the columnview widget."""
|
||||
self.assertIsInstance(self.window._scroll, Gtk.ScrolledWindow)
|
||||
self.assertIsInstance(self.window._selection, Gtk.NoSelection)
|
||||
self.assertIsInstance(self.window._view, Gtk.ColumnView)
|
||||
|
||||
self.assertEqual(self.window._scroll.get_policy(),
|
||||
(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC))
|
||||
|
||||
self.assertEqual(self.window._view.get_model(), self.window._selection)
|
||||
self.assertEqual(self.window._selection.get_model(), self.sql.settings)
|
||||
self.assertTrue(self.window._view.get_show_row_separators())
|
||||
|
||||
self.assertEqual(self.window._header.get_next_sibling(),
|
||||
self.window._scroll)
|
||||
self.assertEqual(self.window._scroll.get_child(), self.window._view)
|
||||
|
||||
def test_columns(self):
|
||||
"""Test the view's columns."""
|
||||
columns = self.window._view.get_columns()
|
||||
self.assertEqual(len(columns), 2)
|
||||
|
||||
self.assertIsInstance(columns[0].get_factory(),
|
||||
emmental.factory.InscriptionFactory)
|
||||
self.assertEqual(columns[0].get_title(), "Key")
|
||||
self.assertEqual(columns[0].get_fixed_width(), 400)
|
||||
|
||||
self.assertIsInstance(columns[1].get_factory(),
|
||||
emmental.factory.Factory)
|
||||
self.assertEqual(columns[1].get_factory().row_type,
|
||||
emmental.header.settings.ValueRow)
|
||||
self.assertEqual(columns[1].get_title(), "Value")
|
||||
self.assertEqual(columns[1].get_fixed_width(), 100)
|
Loading…
Reference in New Issue