Compare commits
13 Commits
c5f9608c49
...
a6cd453c63
Author | SHA1 | Date |
---|---|---|
Anna Schumaker | a6cd453c63 | |
Anna Schumaker | 7079076857 | |
Anna Schumaker | 8d72e1375f | |
Anna Schumaker | 87b92ffc90 | |
Anna Schumaker | bb4ca1e9c4 | |
Anna Schumaker | 087c378e59 | |
Anna Schumaker | 1f434358de | |
Anna Schumaker | 41cb325ad0 | |
Anna Schumaker | 0c1e5fcace | |
Anna Schumaker | 9cb927aabb | |
Anna Schumaker | 397c693aef | |
Anna Schumaker | eb162154b5 | |
Anna Schumaker | 2b5cdaa197 |
|
@ -3,6 +3,7 @@
|
|||
import musicbrainzngs
|
||||
import pathlib
|
||||
from . import gsetup
|
||||
from . import action
|
||||
from . import audio
|
||||
from . import db
|
||||
from . import header
|
||||
|
@ -20,7 +21,7 @@ from gi.repository import Adw
|
|||
|
||||
MAJOR_VERSION = 3
|
||||
MINOR_VERSION = 0
|
||||
MICRO_VERSION = 1
|
||||
MICRO_VERSION = 2
|
||||
|
||||
VERSION_NUMBER = f"{MAJOR_VERSION}.{MINOR_VERSION}.{MICRO_VERSION}"
|
||||
VERSION_STRING = f"Emmental {VERSION_NUMBER}{gsetup.DEBUG_STR}"
|
||||
|
@ -44,6 +45,11 @@ class Application(Adw.Application):
|
|||
flags=Gio.ApplicationFlags.HANDLES_OPEN)
|
||||
self.add_main_option_entries([options.Version])
|
||||
|
||||
def __add_accelerators(self, accels: list[action.ActionEntry]) -> None:
|
||||
for entry in accels:
|
||||
self.add_action(entry.action)
|
||||
self.set_accels_for_action(f"app.{entry.name}", entry.accels)
|
||||
|
||||
def __load_file(self, file: pathlib.Path,
|
||||
*, gapless: bool = False) -> None:
|
||||
self.__stop_current_track()
|
||||
|
@ -136,6 +142,8 @@ class Application(Adw.Application):
|
|||
("audio.replaygain.mode", "rg-mode")]:
|
||||
self.db.settings.bind_setting(setting, hdr, property)
|
||||
|
||||
self.__add_accelerators(hdr.accelerators)
|
||||
|
||||
hdr.connect("notify::rg-enabled", self.__set_replaygain)
|
||||
hdr.connect("notify::rg-mode", self.__set_replaygain)
|
||||
hdr.connect("track-requested", self.__load_path)
|
||||
|
@ -159,6 +167,8 @@ class Application(Adw.Application):
|
|||
self.db.settings.bind_setting("now-playing.prefer-artist",
|
||||
playing, "prefer-artist")
|
||||
|
||||
self.__add_accelerators(playing.accelerators)
|
||||
|
||||
playing.connect("jump", self.__on_jump)
|
||||
playing.connect("play", self.player.play)
|
||||
playing.connect("pause", self.player.pause)
|
||||
|
@ -193,6 +203,7 @@ class Application(Adw.Application):
|
|||
now_playing=self.build_now_playing(),
|
||||
sidebar=self.build_sidebar(),
|
||||
tracklist=self.build_tracklist())
|
||||
win.bind_property("user-editing", win.now_playing, "editing")
|
||||
|
||||
for (setting, property) in [("window.width", "default-width"),
|
||||
("window.height", "default-height"),
|
||||
|
@ -200,6 +211,7 @@ class Application(Adw.Application):
|
|||
("sidebar.size", "sidebar-size")]:
|
||||
self.db.settings.bind_setting(setting, win, property)
|
||||
|
||||
self.__add_accelerators(win.accelerators)
|
||||
return win
|
||||
|
||||
def connect_mpris2(self) -> None:
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""A custom ActionEntry that works in Python."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class ActionEntry(GObject.GObject):
|
||||
"""Our own AcitionEntry class to make accelerators easier."""
|
||||
|
||||
enabled = GObject.Property(type=bool, default=True)
|
||||
|
||||
def __init__(self, name: str, func: callable, *accels: tuple[str],
|
||||
enabled: tuple[GObject.GObject, str] | None = None):
|
||||
"""Initialize an ActionEntry."""
|
||||
super().__init__()
|
||||
|
||||
for accel in accels:
|
||||
if not Gtk.accelerator_parse(accel)[0]:
|
||||
raise ValueError
|
||||
|
||||
self.accels = list(accels)
|
||||
self.func = func
|
||||
|
||||
if enabled is not None:
|
||||
self.enabled = enabled[0].get_property(enabled[1])
|
||||
enabled[0].bind_property(enabled[1], self, "enabled")
|
||||
|
||||
self.action = Gio.SimpleAction(name=name, enabled=self.enabled)
|
||||
self.action.connect("activate", self.__activate)
|
||||
self.bind_property("enabled", self.action, "enabled")
|
||||
|
||||
def __activate(self, action: Gio.SimpleAction, param) -> None:
|
||||
self.func()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Get then name of this ActionEntry."""
|
||||
return self.action.get_name()
|
|
@ -59,6 +59,7 @@ class SplitButton(Gtk.Box):
|
|||
|
||||
self.bind_property("icon-name", self._primary, "icon-name")
|
||||
self.bind_property("icon-size", self._primary, "icon-size")
|
||||
self._primary.connect("activate", self.__activate)
|
||||
self._primary.connect("clicked", self.__clicked)
|
||||
|
||||
self.append(self._primary)
|
||||
|
@ -67,14 +68,24 @@ class SplitButton(Gtk.Box):
|
|||
|
||||
self.add_css_class("emmental-splitbutton")
|
||||
|
||||
def __activate(self, button: Button) -> None:
|
||||
self.emit("activate-primary")
|
||||
|
||||
def __clicked(self, button: Button) -> None:
|
||||
self.emit("clicked")
|
||||
|
||||
def activate(self, *args) -> None:
|
||||
self._primary.activate()
|
||||
|
||||
@GObject.Property(type=Gtk.Button, flags=GObject.ParamFlags.READABLE)
|
||||
def secondary(self) -> Gtk.Button:
|
||||
"""Get the secondary button attached to the SplitButton."""
|
||||
return self._secondary
|
||||
|
||||
@GObject.Signal
|
||||
def activate_primary(self) -> None:
|
||||
"""Signal that the primary button has been activated."""
|
||||
|
||||
@GObject.Signal
|
||||
def clicked(self) -> None:
|
||||
"""Signal that the primary button has been clicked."""
|
||||
|
|
|
@ -5,6 +5,7 @@ import typing
|
|||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
from ..action import ActionEntry
|
||||
from .. import db
|
||||
from .. import buttons
|
||||
from .. import gsetup
|
||||
|
@ -118,6 +119,21 @@ class Header(Gtk.HeaderBar):
|
|||
path: pathlib.Path) -> None:
|
||||
self.emit("track-requested", path)
|
||||
|
||||
@property
|
||||
def accelerators(self) -> list[ActionEntry]:
|
||||
"""Get a list of accelerators for the Header."""
|
||||
res = [ActionEntry("open-file", self._open.activate, "<Control>o"),
|
||||
ActionEntry("decrease-volume", self._volume.decrement,
|
||||
"<Control>Down"),
|
||||
ActionEntry("increase-volume", self._volume.increment,
|
||||
"<Control>Up"),
|
||||
ActionEntry("toggle-bg-mode", self._background.activate,
|
||||
"<Shift><Control>b")]
|
||||
if __debug__:
|
||||
res.append(ActionEntry("edit-settings", self._settings.activate,
|
||||
"<Shift><Control>s"))
|
||||
return res
|
||||
|
||||
@GObject.Signal(arg_types=(GObject.TYPE_PYOBJECT,))
|
||||
def track_requested(self, path: pathlib.Path) -> None:
|
||||
"""Signal that a track has been requested."""
|
||||
|
|
|
@ -41,17 +41,19 @@ class VolumeRow(Gtk.ListBoxRow):
|
|||
self._box.append(self._increment)
|
||||
self.set_child(self._box)
|
||||
|
||||
self._decrement.connect("clicked", self.__decrement)
|
||||
self._decrement.connect("clicked", self.decrement)
|
||||
self._scale.connect("value-changed", self.__value_changed)
|
||||
self._increment.connect("clicked", self.__increment)
|
||||
self._increment.connect("clicked", self.increment)
|
||||
|
||||
self.bind_property("volume", self._adjustment, "value",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
||||
def __decrement(self, button: Gtk.Button) -> None:
|
||||
def decrement(self, button: Gtk.Button | None = None) -> None:
|
||||
"""Decrease the volume by STEP_SIZE."""
|
||||
self._scale.set_value(self._scale.get_value() - STEP_SIZE)
|
||||
|
||||
def __increment(self, button: Gtk.Button) -> None:
|
||||
def increment(self, button: Gtk.Button | None = None) -> None:
|
||||
"""Increase the volume by STEP_SIZE."""
|
||||
self._scale.set_value(self._scale.get_value() + STEP_SIZE)
|
||||
|
||||
def __value_changed(self, range: Gtk.Range) -> None:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"""A card for displaying information about the currently playing track."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from ..action import ActionEntry
|
||||
from .. import buttons
|
||||
from . import artwork
|
||||
from . import controls
|
||||
|
@ -28,6 +29,7 @@ class Card(Gtk.Box):
|
|||
have_previous = GObject.Property(type=bool, default=False)
|
||||
have_track = GObject.Property(type=bool, default=False)
|
||||
have_db_track = GObject.Property(type=bool, default=False)
|
||||
editing = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize a Now Playing Card."""
|
||||
|
@ -39,11 +41,14 @@ class Card(Gtk.Box):
|
|||
self._bottom_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
|
||||
self._favorite = buttons.ImageToggle("heart-filled",
|
||||
"heart-outline-thick-symbolic",
|
||||
tooltip_text="add to "
|
||||
"'Favorite Tracks'",
|
||||
icon_size=Gtk.IconSize.LARGE,
|
||||
has_frame=False, sensitive=False,
|
||||
valign=Gtk.Align.CENTER)
|
||||
self._jump = buttons.Button(icon_name="go-jump", has_frame=False,
|
||||
icon_size=Gtk.IconSize.LARGE,
|
||||
tooltip_text="scroll to current track",
|
||||
valign=Gtk.Align.CENTER, sensitive=False)
|
||||
self._seeker = seeker.Scale(sensitive=False)
|
||||
|
||||
|
@ -52,7 +57,8 @@ class Card(Gtk.Box):
|
|||
self.bind_property(prop, self._tags, prop)
|
||||
self.bind_property("prefer-artist", self._tags, "prefer-artist",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
for prop in ["playing", "have-next", "have-previous", "have-track"]:
|
||||
for prop in ["playing", "editing", "have-next",
|
||||
"have-previous", "have-track"]:
|
||||
self.bind_property(prop, self._controls, prop)
|
||||
self.bind_property("have-db-track", self._jump, "sensitive")
|
||||
self.bind_property("have-db-track", self._favorite, "sensitive")
|
||||
|
@ -90,6 +96,29 @@ class Card(Gtk.Box):
|
|||
value: float) -> None:
|
||||
self.emit("seek", value)
|
||||
|
||||
@property
|
||||
def accelerators(self) -> list[ActionEntry]:
|
||||
"""Get a list of accelerators for the Now Playing card."""
|
||||
return [ActionEntry("toggle-favorite", self._favorite.activate,
|
||||
"<Control>f", enabled=(self, "have-db-track")),
|
||||
ActionEntry("goto-current-track", self._jump.activate,
|
||||
"<Control>g", enabled=(self, "have-db-track")),
|
||||
ActionEntry("next-track", self._controls.activate_next,
|
||||
"Return", enabled=(self._controls,
|
||||
"can-activate-next")),
|
||||
ActionEntry("previous-track", self._controls.activate_previous,
|
||||
"BackSpace", enabled=(self._controls,
|
||||
"can-activate-prev")),
|
||||
ActionEntry("play-pause", self._controls.activate_play_pause,
|
||||
"space", enabled=(self._controls,
|
||||
"can-activate-play-pause")),
|
||||
ActionEntry("inc-autopause", self._controls.increase_autopause,
|
||||
"<Control>plus", "<Control>KP_Add",
|
||||
enabled=(self, "playing")),
|
||||
ActionEntry("dec-autopause", self._controls.decrease_autopause,
|
||||
"<Control>minus", "<Control>KP_Subtract",
|
||||
enabled=(self, "playing"))]
|
||||
|
||||
@GObject.Signal
|
||||
def jump(self) -> None:
|
||||
"""Signal that the Tracklist should be scrolled."""
|
||||
|
|
|
@ -8,39 +8,40 @@ from .. import gsetup
|
|||
FALLBACK_RESOURCE = f"{gsetup.RESOURCE_ICONS}/emmental.svg"
|
||||
|
||||
|
||||
class Artwork(Gtk.Frame):
|
||||
"""Our custom Album Art widget that draws a border around a picture."""
|
||||
class Artwork(Gtk.Picture):
|
||||
"""Our custom Album Art widget takes a pathlib.Path."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the Album Art widget."""
|
||||
super().__init__(margin_top=6, margin_bottom=6, margin_start=6,
|
||||
margin_end=6, halign=Gtk.Align.CENTER,
|
||||
valign=Gtk.Align.CENTER)
|
||||
self._picture = Gtk.Picture(content_fit=Gtk.ContentFit.CONTAIN)
|
||||
super().__init__(content_fit=Gtk.ContentFit.CONTAIN,
|
||||
margin_top=6, margin_bottom=6,
|
||||
margin_start=6, margin_end=6,
|
||||
halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)
|
||||
self._fullsize = Gtk.Picture(content_fit=Gtk.ContentFit.FILL)
|
||||
self._popover = Gtk.Popover(child=self._fullsize)
|
||||
self._clicked = Gtk.GestureClick()
|
||||
|
||||
self._popover = Gtk.Popover(child=self._fullsize)
|
||||
self._popover.set_parent(self)
|
||||
|
||||
self._clicked = Gtk.GestureClick()
|
||||
self._clicked.connect("released", self.clicked)
|
||||
self.add_controller(self._clicked)
|
||||
|
||||
self._popover.set_parent(self)
|
||||
self.set_child(self._picture)
|
||||
self.add_css_class("card")
|
||||
self.filepath = None
|
||||
|
||||
@GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
def filepath(self) -> pathlib.Path:
|
||||
"""Get the current artwork path."""
|
||||
name = self._picture.get_file().get_parse_name()
|
||||
name = self.get_file().get_parse_name()
|
||||
return None if name.startswith("resource:") else pathlib.Path(name)
|
||||
|
||||
@filepath.setter
|
||||
def filepath(self, path: pathlib.Path) -> None:
|
||||
if path is not None:
|
||||
self._picture.set_filename(str(path))
|
||||
self.set_filename(str(path))
|
||||
self._fullsize.set_filename(str(path))
|
||||
else:
|
||||
self._picture.set_resource(FALLBACK_RESOURCE)
|
||||
self.set_resource(FALLBACK_RESOURCE)
|
||||
self._fullsize.set_resource(FALLBACK_RESOURCE)
|
||||
|
||||
def clicked(self, gesture: Gtk.GestureClick, n_press: int,
|
||||
|
|
|
@ -25,6 +25,8 @@ class Entry(Gtk.Entry):
|
|||
self.connect("icon_release", self.__icon_release)
|
||||
self.connect("notify::value", self.__update_text)
|
||||
|
||||
self.add_css_class("card")
|
||||
|
||||
def __set_value(self, newval: int) -> bool:
|
||||
if -1 <= newval <= 99:
|
||||
self.value = newval
|
||||
|
@ -89,6 +91,16 @@ class Entry(Gtk.Entry):
|
|||
self.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY,
|
||||
self.value < 99)
|
||||
|
||||
def decrement(self) -> None:
|
||||
"""Decrease the autopause count by 1."""
|
||||
if self.value > -1:
|
||||
self.value -= 1
|
||||
|
||||
def increment(self) -> None:
|
||||
"""Increase the autopause count by 1."""
|
||||
if self.value < 99:
|
||||
self.value += 1
|
||||
|
||||
|
||||
class Button(buttons.PopoverButton):
|
||||
"""A PopoverButton that displays Autopause count."""
|
||||
|
@ -114,3 +126,11 @@ class Button(buttons.PopoverButton):
|
|||
def __notify_value(self, button: buttons.PopoverButton, param) -> None:
|
||||
text = str(self.value) if self.value > -1 else ""
|
||||
self._count.set_markup(f"<small>{text}</small>")
|
||||
|
||||
def decrement(self) -> None:
|
||||
"""Decrease the autopause value."""
|
||||
self.popover_child.decrement()
|
||||
|
||||
def increment(self) -> None:
|
||||
"""Increase the autopause value."""
|
||||
self.popover_child.increment()
|
||||
|
|
|
@ -28,6 +28,11 @@ class Controls(Gtk.Box):
|
|||
have_previous = GObject.Property(type=bool, default=False)
|
||||
have_track = GObject.Property(type=bool, default=False)
|
||||
|
||||
editing = GObject.Property(type=bool, default=False)
|
||||
can_activate_next = GObject.Property(type=bool, default=False)
|
||||
can_activate_prev = GObject.Property(type=bool, default=False)
|
||||
can_activate_play_pause = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the Controls."""
|
||||
super().__init__(valign=Gtk.Align.START, homogeneous=True,
|
||||
|
@ -37,14 +42,16 @@ class Controls(Gtk.Box):
|
|||
|
||||
self._autopause = autopause.Button()
|
||||
self._prev = PillButton(icon_name="media-skip-backward",
|
||||
tooltip_text="previous track", sensitive=False)
|
||||
self._play = PillButton(icon_name="play-large", tooltip_text="play",
|
||||
sensitive=False)
|
||||
self._play = PillButton(icon_name="play-large", sensitive=False)
|
||||
self._pause = buttons.SplitButton(icon_name="pause-large",
|
||||
icon_size=Gtk.IconSize.LARGE,
|
||||
tooltip_text="pause",
|
||||
secondary=self._autopause,
|
||||
visible=False, sensitive=False)
|
||||
self._next = PillButton(icon_name="media-skip-forward",
|
||||
sensitive=False)
|
||||
tooltip_text="next track", sensitive=False)
|
||||
|
||||
for button in [self._prev, self._play, self._pause, self._next]:
|
||||
self.append(button)
|
||||
|
@ -63,19 +70,59 @@ class Controls(Gtk.Box):
|
|||
self.bind_property("have-previous", self._prev, "sensitive")
|
||||
self.bind_property("have-track", self._play, "sensitive")
|
||||
self.bind_property("have-track", self._pause, "sensitive")
|
||||
self.connect("notify::playing", self.__notify_playing)
|
||||
self.connect("notify", self.__notify)
|
||||
|
||||
self.add_css_class("linked")
|
||||
|
||||
def __on_click(self, button: Gtk.Button, signal: str) -> None:
|
||||
self.emit(signal)
|
||||
|
||||
def __notify_playing(self, controls, param) -> None:
|
||||
def __notify_playing(self, controls: Gtk.Box, param) -> None:
|
||||
if not self.playing and self.autopause != -1:
|
||||
if win := self.get_ancestor(window.Window):
|
||||
win.post_toast("Autopause Cancelled")
|
||||
self.autopause = -1
|
||||
|
||||
def __notify(self, controls: Gtk.Box, param) -> None:
|
||||
match param.name:
|
||||
case "editing":
|
||||
allowed = not self.editing
|
||||
self.can_activate_next = self.have_next and allowed
|
||||
self.can_activate_prev = self.have_previous and allowed
|
||||
self.can_activate_play_pause = self.have_track and allowed
|
||||
case "playing": self.__notify_playing(controls, param)
|
||||
case "have-next":
|
||||
self.can_activate_next = self.have_next and not self.editing
|
||||
case "have-previous":
|
||||
can_activate = self.have_previous and not self.editing
|
||||
self.can_activate_prev = can_activate
|
||||
case "have-track":
|
||||
can_activate = self.have_track and not self.editing
|
||||
self.can_activate_play_pause = can_activate
|
||||
|
||||
def activate_next(self) -> None:
|
||||
"""Activate the Next button."""
|
||||
self._next.activate()
|
||||
|
||||
def activate_previous(self) -> None:
|
||||
"""Activate the Previous button."""
|
||||
self._prev.activate()
|
||||
|
||||
def activate_play_pause(self) -> None:
|
||||
"""Activate the Play or Pause button."""
|
||||
if self.playing:
|
||||
self._pause.activate()
|
||||
else:
|
||||
self._play.activate()
|
||||
|
||||
def decrease_autopause(self) -> None:
|
||||
"""Decrease the autopause count."""
|
||||
self._autopause.decrement()
|
||||
|
||||
def increase_autopause(self) -> None:
|
||||
"""Increase the autopause count."""
|
||||
self._autopause.increment()
|
||||
|
||||
@GObject.Signal
|
||||
def previous(self) -> None:
|
||||
"""Signals that the Previous button has been clicked."""
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
from .action import ActionEntry
|
||||
|
||||
|
||||
def _make_pane(orientation: Gtk.Orientation, position: int = 0,
|
||||
|
@ -32,6 +33,7 @@ class Window(Adw.Window):
|
|||
now_playing = GObject.Property(type=Gtk.Widget)
|
||||
now_playing_size = GObject.Property(type=int, default=250)
|
||||
tracklist = GObject.Property(type=Gtk.Widget)
|
||||
user_editing = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self, version: str, **kwargs):
|
||||
"""Initialize our Window."""
|
||||
|
@ -62,10 +64,16 @@ class Window(Adw.Window):
|
|||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("tracklist", self._inner_pane, "end-child")
|
||||
|
||||
self.connect("notify::focus-widget", self.__notify_focus_widget)
|
||||
|
||||
self._box.append(self._header)
|
||||
self._box.append(self._toast)
|
||||
self.set_content(self._box)
|
||||
|
||||
def __notify_focus_widget(self, win: Gtk.Window, param) -> None:
|
||||
self.user_editing = isinstance(win.get_property("focus-widget"),
|
||||
Gtk.Editable)
|
||||
|
||||
def close(self, *args) -> None:
|
||||
"""Close the window."""
|
||||
super().close()
|
||||
|
@ -79,3 +87,8 @@ class Window(Adw.Window):
|
|||
def present(self, *args) -> None:
|
||||
"""Present the window."""
|
||||
super().present()
|
||||
|
||||
@property
|
||||
def accelerators(self) -> list[ActionEntry]:
|
||||
"""Get a list of accelerators for the Window."""
|
||||
return [ActionEntry("reset-focus", self.set_focus, "Escape")]
|
||||
|
|
|
@ -180,3 +180,27 @@ class TestHeader(tests.util.TestCase):
|
|||
self.header._background)
|
||||
self.assertEqual(self.header._box.get_row_at_index(2),
|
||||
self.header._replaygain)
|
||||
|
||||
def test_accelerators(self):
|
||||
"""Check that the accelerators list is set up properly."""
|
||||
entries = [("open-file", self.header._open.activate, "<Control>o"),
|
||||
("decrease-volume", self.header._volume.decrement,
|
||||
"<Control>Down"),
|
||||
("increase-volume", self.header._volume.increment,
|
||||
"<Control>Up"),
|
||||
("toggle-bg-mode", self.header._background.activate,
|
||||
"<Shift><Control>b"),
|
||||
("edit-settings", self.header._settings.activate,
|
||||
"<Shift><Control>s")]
|
||||
|
||||
accels = self.header.accelerators
|
||||
self.assertIsInstance(accels, list)
|
||||
|
||||
for i, (name, func, accel) in enumerate(entries):
|
||||
with self.subTest(name=name):
|
||||
self.assertIsInstance(accels[i], emmental.action.ActionEntry)
|
||||
self.assertEqual(accels[i].name, name)
|
||||
self.assertEqual(accels[i].func, func)
|
||||
self.assertListEqual(accels[i].accels, [accel])
|
||||
|
||||
self.assertEqual(len(accels), i + 1)
|
||||
|
|
|
@ -57,6 +57,11 @@ class TestVolumeRow(unittest.TestCase):
|
|||
self.assertAlmostEqual(self.vol._scale.get_value(), 0.95)
|
||||
self.assertAlmostEqual(self.value.value, 0.95)
|
||||
|
||||
self.vol.decrement()
|
||||
self.assertAlmostEqual(self.vol.volume, 0.90)
|
||||
self.assertAlmostEqual(self.vol._scale.get_value(), 0.90)
|
||||
self.assertAlmostEqual(self.value.value, 0.90)
|
||||
|
||||
def test_scale(self):
|
||||
"""Check that the volume slider has been set up properly."""
|
||||
self.assertIsInstance(self.vol._adjustment, Gtk.Adjustment)
|
||||
|
@ -103,6 +108,11 @@ class TestVolumeRow(unittest.TestCase):
|
|||
self.assertAlmostEqual(self.vol._scale.get_value(), 0.95)
|
||||
self.assertAlmostEqual(self.value.value, 0.95)
|
||||
|
||||
self.vol.increment()
|
||||
self.assertAlmostEqual(self.vol.volume, 1.0)
|
||||
self.assertAlmostEqual(self.vol._scale.get_value(), 1.0)
|
||||
self.assertAlmostEqual(self.value.value, 1.0)
|
||||
|
||||
def test_format_value(self):
|
||||
"""Check that the scale value is formatted correctly."""
|
||||
format_value = emmental.header.volume.format_value_func
|
||||
|
|
|
@ -21,7 +21,9 @@ class TestArtwork(unittest.TestCase):
|
|||
|
||||
def test_init(self):
|
||||
"""Test that the artwork widget is configured correctly."""
|
||||
self.assertIsInstance(self.artwork, Gtk.Frame)
|
||||
self.assertIsInstance(self.artwork, Gtk.Picture)
|
||||
self.assertEqual(self.artwork.get_content_fit(),
|
||||
Gtk.ContentFit.CONTAIN)
|
||||
|
||||
self.assertEqual(self.artwork.get_halign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(self.artwork.get_valign(), Gtk.Align.CENTER)
|
||||
|
@ -31,27 +33,24 @@ class TestArtwork(unittest.TestCase):
|
|||
self.assertEqual(self.artwork.get_margin_start(), 6)
|
||||
self.assertEqual(self.artwork.get_margin_end(), 6)
|
||||
|
||||
def test_picture(self):
|
||||
"""Test that the artwork picture is configured correctly."""
|
||||
self.assertIsInstance(self.artwork._picture, Gtk.Picture)
|
||||
self.assertEqual(self.artwork._picture.get_content_fit(),
|
||||
Gtk.ContentFit.CONTAIN)
|
||||
self.assertEqual(self.artwork.get_child(), self.artwork._picture)
|
||||
self.assertTrue(self.artwork.has_css_class("card"))
|
||||
|
||||
def test_filepath(self):
|
||||
"""Test that the filepath property works as expected."""
|
||||
self.assertIsNone(self.artwork.filepath)
|
||||
self.assertIsNotNone(self.artwork._picture.get_paintable())
|
||||
self.assertEqual(self.artwork._picture.get_file().get_parse_name(),
|
||||
self.assertIsNotNone(self.artwork.get_paintable())
|
||||
self.assertEqual(self.artwork.get_file().get_parse_name(),
|
||||
f"resource://{self.fallback}")
|
||||
|
||||
self.artwork.filepath = tests.util.COVER_JPG
|
||||
self.assertIsNotNone(self.artwork._picture.get_paintable())
|
||||
self.assertEqual(self.artwork._picture.get_file().get_parse_name(),
|
||||
self.assertIsNotNone(self.artwork.get_paintable())
|
||||
self.assertEqual(self.artwork.get_file().get_parse_name(),
|
||||
str(tests.util.COVER_JPG))
|
||||
self.assertEqual(self.artwork.filepath, tests.util.COVER_JPG)
|
||||
|
||||
self.artwork.filepath = None
|
||||
self.assertIsNotNone(self.artwork._picture.get_paintable())
|
||||
self.assertEqual(self.artwork._picture.get_file().get_parse_name(),
|
||||
self.assertIsNotNone(self.artwork.get_paintable())
|
||||
self.assertEqual(self.artwork.get_file().get_parse_name(),
|
||||
f"resource://{self.fallback}")
|
||||
|
||||
def test_fullsize(self):
|
||||
|
|
|
@ -26,6 +26,7 @@ class TestAutopauseEntry(unittest.TestCase):
|
|||
self.assertIsInstance(self.entry, Gtk.Entry)
|
||||
self.assertTupleEqual(self.entry._timeout, (None, None))
|
||||
self.assertEqual(self.entry.get_max_width_chars(), 20)
|
||||
self.assertTrue(self.entry.has_css_class("card"))
|
||||
|
||||
def test_placeholder_text(self):
|
||||
"""Test changing the placeholder text with the value."""
|
||||
|
@ -147,6 +148,26 @@ class TestAutopauseEntry(unittest.TestCase):
|
|||
self.entry.emit("activate")
|
||||
self.assertEqual(self.entry.value, value, f"text=\"{text}\"")
|
||||
|
||||
def test_decrement(self):
|
||||
"""Test the decrement() function."""
|
||||
self.entry.value = 1
|
||||
self.entry.decrement()
|
||||
self.assertEqual(self.entry.value, 0)
|
||||
self.entry.decrement()
|
||||
self.assertEqual(self.entry.value, -1)
|
||||
self.entry.decrement()
|
||||
self.assertEqual(self.entry.value, -1)
|
||||
|
||||
def test_increment(self):
|
||||
"""Test the increment() function."""
|
||||
self.entry.value = 97
|
||||
self.entry.increment()
|
||||
self.assertEqual(self.entry.value, 98)
|
||||
self.entry.increment()
|
||||
self.assertEqual(self.entry.value, 99)
|
||||
self.entry.increment()
|
||||
self.assertEqual(self.entry.value, 99)
|
||||
|
||||
|
||||
class TestAutopauseButton(unittest.TestCase):
|
||||
"""Test our Autopause Popover Button."""
|
||||
|
@ -193,3 +214,17 @@ class TestAutopauseButton(unittest.TestCase):
|
|||
|
||||
self.button.value = -1
|
||||
self.assertEqual(self.button._count.get_text(), "")
|
||||
|
||||
def test_decrement(self):
|
||||
"""Test the decrement() function."""
|
||||
with unittest.mock.patch.object(self.button.popover_child,
|
||||
"decrement") as mock_decrement:
|
||||
self.button.decrement()
|
||||
mock_decrement.assert_called()
|
||||
|
||||
def test_increment(self):
|
||||
"""Test the increment() functions."""
|
||||
with unittest.mock.patch.object(self.button.popover_child,
|
||||
"increment") as mock_increment:
|
||||
self.button.increment()
|
||||
mock_increment.assert_called()
|
||||
|
|
|
@ -47,10 +47,14 @@ class TestControls(unittest.TestCase):
|
|||
self.assertEqual(self.controls.get_margin_end(),
|
||||
emmental.nowplaying.controls.MARGIN)
|
||||
|
||||
self.assertFalse(self.controls.editing)
|
||||
|
||||
def test_previous_button(self):
|
||||
"""Test the previous button."""
|
||||
self.assertIsInstance(self.controls._prev,
|
||||
emmental.nowplaying.controls.PillButton)
|
||||
self.assertEqual(self.controls._prev.get_tooltip_text(),
|
||||
"previous track")
|
||||
self.assertEqual(self.controls._prev.icon_name, "media-skip-backward")
|
||||
self.assertEqual(self.controls.get_first_child(), self.controls._prev)
|
||||
|
||||
|
@ -58,10 +62,24 @@ class TestControls(unittest.TestCase):
|
|||
self.controls._prev.emit("clicked")
|
||||
self.clicked.assert_called_with(self.controls._prev)
|
||||
|
||||
def test_activate_previous(self):
|
||||
"""Test can-activate-prev and the activate_previous() function."""
|
||||
self.assertFalse(self.controls.can_activate_prev)
|
||||
self.controls.have_previous = True
|
||||
self.assertTrue(self.controls.can_activate_prev)
|
||||
self.controls.editing = True
|
||||
self.assertFalse(self.controls.can_activate_prev)
|
||||
|
||||
activate = unittest.mock.Mock()
|
||||
self.controls._prev.connect("activate", activate)
|
||||
self.controls.activate_previous()
|
||||
activate.assert_called()
|
||||
|
||||
def test_play_button(self):
|
||||
"""Test the play button."""
|
||||
self.assertIsInstance(self.controls._play,
|
||||
emmental.nowplaying.controls.PillButton)
|
||||
self.assertEqual(self.controls._play.get_tooltip_text(), "play")
|
||||
self.assertEqual(self.controls._play.icon_name, "play-large")
|
||||
self.assertEqual(self.controls._prev.get_next_sibling(),
|
||||
self.controls._play)
|
||||
|
@ -81,6 +99,7 @@ class TestControls(unittest.TestCase):
|
|||
"""Test the pause button."""
|
||||
self.assertIsInstance(self.controls._pause,
|
||||
emmental.buttons.SplitButton)
|
||||
self.assertEqual(self.controls._pause.get_tooltip_text(), "pause")
|
||||
self.assertEqual(self.controls._pause.icon_name, "pause-large")
|
||||
self.assertEqual(self.controls._pause.icon_size,
|
||||
Gtk.IconSize.LARGE)
|
||||
|
@ -97,6 +116,25 @@ class TestControls(unittest.TestCase):
|
|||
self.controls._pause.emit("clicked")
|
||||
self.clicked.assert_called_with(self.controls._pause)
|
||||
|
||||
def test_activate_play_pause(self):
|
||||
"""Test can-activate-play-pause and the activate_play_pause() func."""
|
||||
self.assertFalse(self.controls.can_activate_play_pause)
|
||||
self.controls.have_track = True
|
||||
self.assertTrue(self.controls.can_activate_play_pause)
|
||||
self.controls.editing = True
|
||||
self.assertFalse(self.controls.can_activate_play_pause)
|
||||
|
||||
play = unittest.mock.Mock()
|
||||
self.controls._play.connect("activate", play)
|
||||
self.controls.activate_play_pause()
|
||||
play.assert_called()
|
||||
|
||||
self.controls.playing = True
|
||||
pause = unittest.mock.Mock()
|
||||
self.controls._pause.connect("activate-primary", pause)
|
||||
self.controls.activate_play_pause()
|
||||
pause.assert_called()
|
||||
|
||||
def test_autopause_button(self):
|
||||
"""Test the autopause button."""
|
||||
self.assertIsInstance(self.controls._autopause,
|
||||
|
@ -116,12 +154,40 @@ class TestControls(unittest.TestCase):
|
|||
"""Test the next button."""
|
||||
self.assertIsInstance(self.controls._next,
|
||||
emmental.nowplaying.controls.PillButton)
|
||||
self.assertEqual(self.controls._next.get_tooltip_text(), "next track")
|
||||
self.assertEqual(self.controls._next.icon_name, "media-skip-forward")
|
||||
|
||||
self.controls._next.connect("clicked", self.clicked)
|
||||
self.controls._next.emit("clicked")
|
||||
self.clicked.assert_called_with(self.controls._next)
|
||||
|
||||
def test_activate_next(self):
|
||||
"""Test can-activate-next and the activate_next() function."""
|
||||
self.assertFalse(self.controls.can_activate_next)
|
||||
self.controls.have_next = True
|
||||
self.assertTrue(self.controls.can_activate_next)
|
||||
self.controls.editing = True
|
||||
self.assertFalse(self.controls.can_activate_next)
|
||||
|
||||
activate = unittest.mock.Mock()
|
||||
self.controls._next.connect("activate", activate)
|
||||
self.controls.activate_next()
|
||||
activate.assert_called()
|
||||
|
||||
def test_decrease_autopause(self):
|
||||
"""Test the decrease_autopause() function."""
|
||||
with unittest.mock.patch.object(self.controls._autopause,
|
||||
"decrement") as mock_decrement:
|
||||
self.controls.decrease_autopause()
|
||||
mock_decrement.assert_called()
|
||||
|
||||
def test_increase_autopause(self):
|
||||
"""Test the increase_autopause() function."""
|
||||
with unittest.mock.patch.object(self.controls._autopause,
|
||||
"increment") as mock_increment:
|
||||
self.controls.increase_autopause()
|
||||
mock_increment.assert_called()
|
||||
|
||||
def test_have_properties(self):
|
||||
"""Test the have_{next, previous, track} properties."""
|
||||
self.assertFalse(self.controls.have_next)
|
||||
|
|
|
@ -91,6 +91,8 @@ class TestNowPlaying(unittest.TestCase):
|
|||
self.assertEqual(self.card._favorite.active_icon_name, "heart-filled")
|
||||
self.assertEqual(self.card._favorite.inactive_icon_name,
|
||||
"heart-outline-thick-symbolic")
|
||||
self.assertEqual(self.card._favorite.get_tooltip_text(),
|
||||
"add to 'Favorite Tracks'")
|
||||
self.assertEqual(self.card._favorite.icon_size, Gtk.IconSize.LARGE)
|
||||
self.assertEqual(self.card._favorite.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertFalse(self.card._favorite.get_has_frame())
|
||||
|
@ -112,6 +114,8 @@ class TestNowPlaying(unittest.TestCase):
|
|||
self.card._jump)
|
||||
|
||||
self.assertEqual(self.card._jump.icon_name, "go-jump")
|
||||
self.assertEqual(self.card._jump.get_tooltip_text(),
|
||||
"scroll to current track")
|
||||
self.assertEqual(self.card._jump.icon_size, Gtk.IconSize.LARGE)
|
||||
self.assertEqual(self.card._jump.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertFalse(self.card._jump.get_has_frame())
|
||||
|
@ -141,6 +145,14 @@ class TestNowPlaying(unittest.TestCase):
|
|||
self.card._seeker.emit("change-value", Gtk.ScrollType.JUMP, 10)
|
||||
handler.assert_called_with(self.card, 10)
|
||||
|
||||
def test_editing(self):
|
||||
"""Test the 'editing' property."""
|
||||
self.assertFalse(self.card.editing)
|
||||
self.card.editing = True
|
||||
self.assertTrue(self.card._controls.editing)
|
||||
self.card.editing = False
|
||||
self.assertFalse(self.card._controls.editing)
|
||||
|
||||
def test_playing(self):
|
||||
"""Test the 'playing' property."""
|
||||
self.assertFalse(self.card.playing)
|
||||
|
@ -150,7 +162,7 @@ class TestNowPlaying(unittest.TestCase):
|
|||
self.assertFalse(self.card._controls.playing)
|
||||
|
||||
def test_have_properties(self):
|
||||
"""Test the 'have-{next, previous, track} properties."""
|
||||
"""Test the 'have-{next, previous, track}' properties."""
|
||||
for property in ["have-next", "have-previous", "have-track"]:
|
||||
with self.subTest(property=property):
|
||||
self.assertFalse(self.card.get_property(property))
|
||||
|
@ -178,3 +190,39 @@ class TestNowPlaying(unittest.TestCase):
|
|||
self.assertEqual(self.card.position, 0)
|
||||
self.card.position = 0.5
|
||||
self.assertEqual(self.card._seeker.position, 0.5)
|
||||
|
||||
def test_accelerators(self):
|
||||
"""Check that the accelerators list is set up properly."""
|
||||
entries = [("toggle-favorite", self.card._favorite.activate,
|
||||
["<Control>f"], self.card, "have-db-track"),
|
||||
("goto-current-track", self.card._jump.activate,
|
||||
["<Control>g"], self.card, "have-db-track"),
|
||||
("next-track", self.card._controls.activate_next,
|
||||
["Return"], self.card._controls, "can-activate-next"),
|
||||
("previous-track", self.card._controls.activate_previous,
|
||||
["BackSpace"], self.card._controls, "can-activate-prev"),
|
||||
("play-pause", self.card._controls.activate_play_pause,
|
||||
["space"], self.card._controls, "can-activate-play-pause"),
|
||||
("inc-autopause", self.card._controls.increase_autopause,
|
||||
["<Control>plus", "<Control>KP_Add"],
|
||||
self.card, "playing"),
|
||||
("dec-autopause", self.card._controls.decrease_autopause,
|
||||
["<Control>minus", "<Control>KP_Subtract"],
|
||||
self.card, "playing")]
|
||||
|
||||
accels = self.card.accelerators
|
||||
self.assertIsInstance(accels, list)
|
||||
|
||||
for i, (name, func, accel, gobject, prop) in enumerate(entries):
|
||||
with self.subTest(action=name):
|
||||
self.assertIsInstance(accels[i], emmental.action.ActionEntry)
|
||||
self.assertEqual(accels[i].name, name)
|
||||
self.assertEqual(accels[i].func, func)
|
||||
self.assertListEqual(accels[i].accels, accel)
|
||||
|
||||
enabled = gobject.get_property(prop)
|
||||
self.assertEqual(accels[i].enabled, enabled)
|
||||
gobject.set_property(prop, not enabled)
|
||||
self.assertEqual(accels[i].enabled, not enabled)
|
||||
|
||||
self.assertEqual(len(accels), i + 1)
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our Python ActionEntry class."""
|
||||
import unittest
|
||||
import emmental.action
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestActionEntry(unittest.TestCase):
|
||||
"""Test case for our Python ActionEntry."""
|
||||
|
||||
def test_init(self):
|
||||
"""Test constructing an ActionEntry."""
|
||||
func = unittest.mock.Mock()
|
||||
entry = emmental.action.ActionEntry("test-name", func, "<Control>t")
|
||||
self.assertIsInstance(entry, GObject.GObject)
|
||||
self.assertIsInstance(entry.action, Gio.SimpleAction)
|
||||
self.assertEqual(entry.action.get_name(), "test-name")
|
||||
|
||||
self.assertEqual(entry.name, "test-name")
|
||||
self.assertEqual(entry.func, func)
|
||||
self.assertListEqual(entry.accels, ["<Control>t"])
|
||||
|
||||
def test_multiple_accels(self):
|
||||
"""Test that multiple accelerators can be passed."""
|
||||
func = unittest.mock.Mock()
|
||||
entry = emmental.action.ActionEntry("test-multi", func,
|
||||
"<Control>t", "<Control>u")
|
||||
self.assertListEqual(entry.accels, ["<Control>t", "<Control>u"])
|
||||
|
||||
def test_invalid_accel(self):
|
||||
"""Test that invalid accelerators are caught during construction."""
|
||||
func = unittest.mock.Mock()
|
||||
with self.assertRaises(ValueError):
|
||||
emmental.action.ActionEntry("test-name", func, "<abcde>")
|
||||
|
||||
def test_activate(self):
|
||||
"""Test activating the constructed action."""
|
||||
func = unittest.mock.Mock()
|
||||
entry = emmental.action.ActionEntry("test-name", func, "<Control>t")
|
||||
entry.action.activate()
|
||||
func.assert_called_with()
|
||||
|
||||
def test_enabled(self):
|
||||
"""Test the enabled property."""
|
||||
func = unittest.mock.Mock()
|
||||
entry = emmental.action.ActionEntry("test-name", func, "<Control>t")
|
||||
self.assertTrue(entry.enabled)
|
||||
self.assertTrue(entry.action.get_enabled())
|
||||
|
||||
entry.enabled = False
|
||||
self.assertFalse(entry.action.get_enabled())
|
||||
|
||||
def test_enabled_bind(self):
|
||||
"""Test binding to the enabled property at construction."""
|
||||
func = unittest.mock.Mock()
|
||||
label = Gtk.Label(sensitive=False)
|
||||
entry = emmental.action.ActionEntry("test-name", func, "<Control>t",
|
||||
enabled=(label, "sensitive"))
|
||||
self.assertFalse(entry.enabled)
|
||||
self.assertFalse(entry.action.get_enabled())
|
||||
|
||||
label.set_sensitive(True)
|
||||
self.assertTrue(entry.enabled)
|
||||
self.assertTrue(entry.action.get_enabled())
|
|
@ -148,6 +148,18 @@ class TestSplitButton(unittest.TestCase):
|
|||
self.button._primary.emit("clicked")
|
||||
on_click.assert_called_with(self.button)
|
||||
|
||||
def test_activate(self):
|
||||
"""Test the activate function."""
|
||||
activate = unittest.mock.Mock()
|
||||
primary = unittest.mock.Mock()
|
||||
|
||||
self.button.connect("activate-primary", primary)
|
||||
self.button._primary.connect("activate", activate)
|
||||
|
||||
self.button.activate()
|
||||
activate.assert_called()
|
||||
primary.assert_called()
|
||||
|
||||
|
||||
class TestImageToggle(unittest.TestCase):
|
||||
"""Test an ImageToggle button."""
|
||||
|
|
|
@ -22,9 +22,9 @@ class TestEmmental(unittest.TestCase):
|
|||
"""Check that version constants have been set properly."""
|
||||
self.assertEqual(emmental.MAJOR_VERSION, 3)
|
||||
self.assertEqual(emmental.MINOR_VERSION, 0)
|
||||
self.assertEqual(emmental.MICRO_VERSION, 1)
|
||||
self.assertEqual(emmental.VERSION_NUMBER, "3.0.1")
|
||||
self.assertEqual(emmental.VERSION_STRING, "Emmental 3.0.1-debug")
|
||||
self.assertEqual(emmental.MICRO_VERSION, 2)
|
||||
self.assertEqual(emmental.VERSION_NUMBER, "3.0.2")
|
||||
self.assertEqual(emmental.VERSION_STRING, "Emmental 3.0.2-debug")
|
||||
|
||||
def test_application(self):
|
||||
"""Check that the application instance is initialized properly."""
|
||||
|
@ -63,7 +63,7 @@ class TestEmmental(unittest.TestCase):
|
|||
mock_startup.assert_called()
|
||||
mock_load.assert_called()
|
||||
mock_add_window.assert_called_with(self.application.win)
|
||||
mock_set_useragent.assert_called_with("emmental-debug", "3.0.1")
|
||||
mock_set_useragent.assert_called_with("emmental-debug", "3.0.2")
|
||||
|
||||
@unittest.mock.patch("sys.stdout")
|
||||
@unittest.mock.patch("gi.repository.Adw.Application.add_window")
|
||||
|
@ -115,6 +115,26 @@ class TestEmmental(unittest.TestCase):
|
|||
|
||||
self.assertEqual(win.header.title, emmental.VERSION_STRING)
|
||||
|
||||
self.assertEqual(self.application.get_accels_for_action(
|
||||
"app.reset-focus"), ["Escape"])
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_header_accels(self, mock_stdout: io.StringIO):
|
||||
"""Check that accelerators have been added for header actions."""
|
||||
self.application.db = emmental.db.Connection()
|
||||
self.application.factory = emmental.playlist.Factory(
|
||||
self.application.db)
|
||||
self.application.player = emmental.audio.Player()
|
||||
self.application.build_window()
|
||||
|
||||
for action, accel in [("app.open-file", "<Control>o"),
|
||||
("app.decrease-volume", "<Control>Down"),
|
||||
("app.increase-volume", "<Control>Up"),
|
||||
("app.toggle-bg-mode", "<Shift><Control>b"),
|
||||
("app.edit-settings", "<Shift><Control>s")]:
|
||||
self.assertEqual(self.application.get_accels_for_action(action),
|
||||
[accel])
|
||||
|
||||
@unittest.mock.patch("emmental.audio.Player.pause")
|
||||
@unittest.mock.patch("emmental.audio.Player.play")
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
|
@ -128,6 +148,19 @@ class TestEmmental(unittest.TestCase):
|
|||
self.application.player = emmental.audio.Player()
|
||||
win = self.application.build_window()
|
||||
|
||||
for action, accel in [("app.toggle-favorite", ["<Control>f"]),
|
||||
("app.goto-current-track", ["<Control>g"]),
|
||||
("app.next-track", ["Return"]),
|
||||
("app.previous-track", ["BackSpace"]),
|
||||
("app.play-pause", ["space"]),
|
||||
("app.inc-autopause", ["<Control>plus",
|
||||
"<Control>KP_Add"]),
|
||||
("app.dec-autopause", ["<Control>minus",
|
||||
"<Control>KP_Subtract"])]:
|
||||
with self.subTest(action=action):
|
||||
accels = self.application.get_accels_for_action(action)
|
||||
self.assertListEqual(accels, accel)
|
||||
|
||||
for (property, value) in [("have-track", True), ("playing", True),
|
||||
("duration", 10), ("position", 5),
|
||||
("artwork", "/a/b/c.jpg")]:
|
||||
|
@ -143,6 +176,9 @@ class TestEmmental(unittest.TestCase):
|
|||
self.application.factory.can_go_previous = True
|
||||
self.assertTrue(win.now_playing.have_previous)
|
||||
|
||||
win.user_editing = True
|
||||
self.assertTrue(win.now_playing.editing)
|
||||
|
||||
win.now_playing.emit("play")
|
||||
play_func.assert_called()
|
||||
win.now_playing.emit("pause")
|
||||
|
|
|
@ -132,6 +132,17 @@ class TestWindow(unittest.TestCase):
|
|||
self.assertEqual(window2._inner_pane.get_end_child(),
|
||||
window2.tracklist)
|
||||
|
||||
def test_user_editing(self):
|
||||
"""Test the 'user-editing' property."""
|
||||
self.window.header = Gtk.Entry()
|
||||
self.window.tracklist = Gtk.Button()
|
||||
|
||||
self.assertFalse(self.window.user_editing)
|
||||
self.window.set_focus(self.window.header)
|
||||
self.assertTrue(self.window.user_editing)
|
||||
self.window.set_focus(self.window.tracklist)
|
||||
self.assertFalse(self.window.user_editing)
|
||||
|
||||
def test_post_toast(self):
|
||||
"""Test posting a Toast message to the window."""
|
||||
toast = self.window.post_toast("Test Toast")
|
||||
|
@ -149,3 +160,13 @@ class TestWindow(unittest.TestCase):
|
|||
with unittest.mock.patch.object(Gtk.Window, "present") as mock_present:
|
||||
self.window.present(1, 2, 3, 4, 5)
|
||||
mock_present.assert_called()
|
||||
|
||||
def test_accelerators(self):
|
||||
"""Test that the Window accelerators are set up properly."""
|
||||
accels = self.window.accelerators
|
||||
self.assertIsInstance(accels, list)
|
||||
self.assertEqual(len(accels), 1)
|
||||
|
||||
self.assertEqual(accels[0].name, "reset-focus")
|
||||
self.assertEqual(accels[0].func, self.window.set_focus)
|
||||
self.assertListEqual(accels[0].accels, ["Escape"])
|
||||
|
|
Loading…
Reference in New Issue