Skip to content

Commit

Permalink
Add common keyboard controls (#947)
Browse files Browse the repository at this point in the history
  • Loading branch information
Haaruun-I authored Sep 22, 2024
1 parent 45a1499 commit e5190e1
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 37 deletions.
30 changes: 30 additions & 0 deletions cozy/media/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,12 @@ def forward(self):
if state == Gst.State.PLAYING:
self._gst_player.play()

def volume_up(self):
self.volume = min(1.0, self.volume + 0.1)

def volume_down(self):
self.volume = max(0, self.volume - 0.1)

def destroy(self):
self._gst_player.stop()

Expand Down Expand Up @@ -583,6 +589,30 @@ def _next_chapter(self):
chapter.position = chapter.start_position
self.play_pause_chapter(self._book, chapter)

def _previous_chapter(self):
if not self._book:
log.error("Cannot play previous chapter because no book reference is stored.")
reporter.error(
"player", "Cannot play previous chapter because no book reference is stored."
)
return

index_current_chapter = self._book.chapters.index(self._book.current_chapter)
self._book.current_chapter.position = self._book.current_chapter.start_position

if index_current_chapter - 1 < 0:
log.info("Book reached start, cannot rewind further.")
chapter = self._book.chapters[0]
chapter.position = chapter.start_position

self._load_chapter(chapter)
self.pause()
self._emit_tick()
else:
chapter = self._book.chapters[index_current_chapter - 1]
chapter.position = chapter.start_position
self.play_pause_chapter(self._book, chapter)

def _on_importer_event(self, event: str, message):
if event == "scan" and message == ScanStatus.SUCCESS:
log.info("Reloading current book")
Expand Down
2 changes: 2 additions & 0 deletions cozy/ui/about_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def __init__(self, version: str) -> None:

self.set_extra_credits()

self.connect = self._window.connect

def get_contributors(self) -> list[str]:
authors_file = Gio.resources_lookup_data(
"/com/github/geigi/cozy/appdata/authors.list", Gio.ResourceLookupFlags.NONE
Expand Down
53 changes: 40 additions & 13 deletions cozy/ui/main_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from cozy.ui.library_view import LibraryView
from cozy.ui.preferences_window import PreferencesWindow
from cozy.ui.widgets.first_import_button import FirstImportButton
from cozy.view_model.playback_control_view_model import PlaybackControlViewModel
from cozy.view_model.playback_speed_view_model import PlaybackSpeedViewModel
from cozy.view_model.storages_view_model import StoragesViewModel

log = logging.getLogger("ui")
Expand All @@ -36,6 +38,8 @@ class CozyUI(EventSender, metaclass=Singleton):
_files: Files = inject.attr(Files)
_player: Player = inject.attr(Player)
_storages_view_model: StoragesViewModel = inject.attr(StoragesViewModel)
_playback_control_view_model: PlaybackControlViewModel = inject.attr(PlaybackControlViewModel)
_playback_speed_view_model: PlaybackSpeedViewModel = inject.attr(PlaybackSpeedViewModel)

_library_view: LibraryView

Expand All @@ -44,6 +48,8 @@ def __init__(self, app, version):
self.app = app
self.version = version

self._actions_to_disable = []

def activate(self, library_view: LibraryView):
self.__init_window()
self.__init_actions()
Expand All @@ -55,7 +61,9 @@ def activate(self, library_view: LibraryView):
self.check_for_tracks()

def startup(self):
self.window_builder = Gtk.Builder.new_from_resource("/com/github/geigi/cozy/ui/main_window.ui")
self.window_builder = Gtk.Builder.new_from_resource(
"/com/github/geigi/cozy/ui/main_window.ui"
)
self.window: Adw.ApplicationWindow = self.window_builder.get_object("app_window")

def __init_window(self):
Expand Down Expand Up @@ -90,22 +98,28 @@ def __init_actions(self):
"""
Init all app actions.
"""
self.create_action("about", self.show_about_window, ["F1"])
self.create_action("about", self.show_about_window, ["F1"], global_shorcut=True)
self.create_action("reset_book", self.reset_book)
self.create_action("remove_book", self.remove_book)

self.create_action("mark_book_as_read", self.mark_book_as_read)
self.create_action("jump_to_book_folder", self.jump_to_book_folder)
self.create_action("prefs", self.show_preferences_window, ["<primary>comma"])
self.create_action("quit", self.quit, ["<primary>q", "<primary>w"])

self.create_action("prefs", self.show_preferences_window, ["<primary>comma"], global_shorcut=True)
self.create_action("quit", self.quit, ["<primary>q", "<primary>w"], global_shorcut=True)

self.scan_action = self.create_action("scan", self.scan)
self.play_pause_action = self.create_action("play_pause", self.play_pause, ["space"])

self.hide_offline_action = Gio.SimpleAction.new_stateful(
"hide_offline", None, GLib.Variant.new_boolean(self.application_settings.hide_offline)
)
self.hide_offline_action.connect("change-state", self.__on_hide_offline)
self.app.add_action(self.hide_offline_action)

def set_hotkeys_enabled(self, enabled: bool) -> None:
for action in self._actions_to_disable:
action.set_enabled(enabled)

def __init_components(self):
path = self._settings.default_location.path if self._settings.storage_locations else None
self.import_button = FirstImportButton(self._set_audiobook_path, path)
Expand All @@ -121,6 +135,8 @@ def create_action(
name: str,
callback: Callable[[Gio.SimpleAction, None], None],
shortcuts: list[str] | None = None,
*,
global_shorcut: bool = False,
) -> Gio.SimpleAction:
action = Gio.SimpleAction.new(name, None)
action.connect("activate", callback)
Expand All @@ -129,6 +145,9 @@ def create_action(
if shortcuts:
self.app.set_accels_for_action(f"app.{name}", shortcuts)

if not global_shorcut:
self._actions_to_disable.append(action)

return action

def refresh_library_filters(self):
Expand Down Expand Up @@ -161,14 +180,21 @@ def quit(self, action, parameter):
self.on_close(None)
self.app.quit()

def _dialog_close_callback(self, dialog):
dialog.disconnect_by_func(self._dialog_close_callback)
self.set_hotkeys_enabled(True)

def show_about_window(self, *_):
AboutWindow(self.version).present(self.window)
self.set_hotkeys_enabled(False)
about = AboutWindow(self.version)
about.connect("closed", self._dialog_close_callback)
about.present(self.window)

def show_preferences_window(self, *_):
PreferencesWindow().present(self.window)

def play_pause(self, *_):
self._player.play_pause()
self.set_hotkeys_enabled(False)
prefs = PreferencesWindow()
prefs.connect("closed", self._dialog_close_callback)
prefs.present(self.window)

def block_ui_buttons(self, block, scan=False):
"""
Expand All @@ -177,7 +203,6 @@ def block_ui_buttons(self, block, scan=False):
"""
sensitive = not block
try:
self.play_pause_action.set_enabled(sensitive)
if scan:
self.scan_action.set_enabled(sensitive)
self.hide_offline_action.set_enabled(sensitive)
Expand All @@ -189,7 +214,10 @@ def switch_to_playing(self):
Switch the UI state back to playing.
This enables all UI functionality for the user.
"""
if self.navigation_view.props.visible_page != "book_overview" and self.main_stack.props.visible_child_name != "welcome":
if (
self.navigation_view.props.visible_page != "book_overview"
and self.main_stack.props.visible_child_name != "welcome"
):
self.navigation_view.pop_to_tag("main")

if self._player.loaded_book:
Expand Down Expand Up @@ -289,4 +317,3 @@ def _save_window_size(self, *_):
self.application_settings.window_width = width
self.application_settings.window_height = height
self.application_settings.window_maximize = self.window.is_maximized()

44 changes: 41 additions & 3 deletions cozy/ui/media_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from cozy.ui.widgets.seek_bar import SeekBar
from cozy.ui.widgets.sleep_timer import SleepTimer
from cozy.view_model.playback_control_view_model import PlaybackControlViewModel
from cozy.view_model.playback_speed_view_model import PlaybackSpeedViewModel

log = logging.getLogger("MediaController")

Expand Down Expand Up @@ -57,9 +58,8 @@ def __init__(self, main_window_builder: Gtk.Builder):
]
)

self._playback_control_view_model: PlaybackControlViewModel = inject.instance(
PlaybackControlViewModel
)
self._playback_control_view_model = inject.instance(PlaybackControlViewModel)
self._playback_speed_view_model = inject.instance(PlaybackSpeedViewModel)
self._artwork_cache: ArtworkCache = inject.instance(ArtworkCache)
self._connect_view_model()
self._connect_widgets()
Expand All @@ -69,6 +69,7 @@ def __init__(self, main_window_builder: Gtk.Builder):
self._on_length_changed()
self._on_position_changed()
self._on_volume_changed()
self._setup_shortcuts()

def _connect_view_model(self):
self._playback_control_view_model.bind_to("book", self._on_book_changed)
Expand Down Expand Up @@ -103,6 +104,22 @@ def _set_cover_image(self, book: Book):
self.cover_img.set_from_icon_name("book-open-variant-symbolic")
self.cover_img.props.pixel_size = COVER_SIZE

@inject.param("main_window", "MainWindow")
def _setup_shortcuts(self, main_window):
main_window.create_action("play_pause", self._play_clicked, ["space"])
main_window.create_action("seek_rewind", self._rewind_clicked, ["Left"])
main_window.create_action("seek_forward", self._forward_clicked, ["Right"])

main_window.create_action("volume_up", self._volume_up, ["Up"])
main_window.create_action("volume_down", self._volume_down, ["Down"])

main_window.create_action("speed_up", self._speed_up, ["plus", "KP_Add", "<primary>Up"])
main_window.create_action("speed_down", self._speed_down, ["minus", "KP_Subtract", "<primary>Down"])
main_window.create_action("speed_reset", self._speed_reset, ["equal"])

main_window.create_action("prev_chapter", self._prev_chapter, ["Page_Down", "<primary>Left"])
main_window.create_action("next_chapter", self._next_chapter, ["Page_Up", "<primary>Right"])

def _on_book_changed(self) -> None:
book = self._playback_control_view_model.book
self._set_book(book)
Expand Down Expand Up @@ -145,6 +162,27 @@ def _on_lock_ui_changed(self):
def _on_volume_changed(self):
self.volume_button.set_value(self._playback_control_view_model.volume)

def _volume_up(self, *_):
self._playback_control_view_model.volume_up()

def _volume_down(self, *_):
self._playback_control_view_model.volume_down()

def _speed_up(self, *_):
self._playback_speed_view_model.speed_up()

def _speed_down(self, *_):
self._playback_speed_view_model.speed_down()

def _speed_reset(self, *_):
self._playback_speed_view_model.speed_reset()

def _next_chapter(self, *_):
self._playback_control_view_model.next_chapter()

def _prev_chapter(self, *_):
self._playback_control_view_model.previous_chapter()

def _play_clicked(self, *_):
self._playback_control_view_model.play_pause()

Expand Down
4 changes: 2 additions & 2 deletions cozy/ui/search_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ def __init__(self, main_window_builder: Gtk.Builder, headerbar: Headerbar) -> No
def open(self, *_) -> None:
self.library_stack.set_visible_child(self)
self.search_bar.set_search_mode(True)
self.main_view.play_pause_action.set_enabled(False)
self.main_view.set_hotkeys_enabled(False)

def close(self) -> None:
self.library_stack.set_visible_child(self.split_view)
self.search_bar.set_search_mode(False)
self.main_view.play_pause_action.set_enabled(True)
self.main_view.set_hotkeys_enabled(True)

def on_state_changed(self, widget: Gtk.Widget, param) -> None:
if widget.get_property(param.name):
Expand Down
8 changes: 0 additions & 8 deletions cozy/ui/widgets/book_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,8 @@ def _install_event_controllers(self):
long_press_gesture = Gtk.GestureLongPress()
long_press_gesture.connect("pressed", self._on_long_tap)

key_event_controller = Gtk.EventControllerKey()
key_event_controller.connect("key-pressed", self._on_key_press_event)

self.add_controller(hover_controller)
self.add_controller(long_press_gesture)
self.add_controller(key_event_controller)

def set_playing(self, is_playing):
self.play_button.set_playing(is_playing)
Expand Down Expand Up @@ -160,7 +156,3 @@ def _on_long_tap(self, gesture: Gtk.Gesture, *_):
device = gesture.get_device()
if device and device.get_source() == Gdk.InputSource.TOUCHSCREEN:
self.menu_button.emit("activate")

def _on_key_press_event(self, controller, keyval, *_):
if keyval == Gdk.KEY_Return:
self.emit("open-book-overview", self.book)
12 changes: 1 addition & 11 deletions cozy/ui/widgets/seek_bar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from gi.repository import Gdk, GObject, Gtk
from gi.repository import GObject, Gtk

from cozy.control.time_format import ns_to_time

Expand Down Expand Up @@ -37,10 +37,6 @@ def __init__(self, **kwargs):
click_gesture.connect("pressed", self._on_progress_scale_press)
click_gesture.connect("released", self._on_progress_scale_release)

keyboard_controller = Gtk.EventControllerKey()
keyboard_controller.connect("key-pressed", self._on_progress_key_pressed)
self.progress_scale.add_controller(keyboard_controller)

@GObject.Signal(arg_types=(object,))
def position_changed(self, *_): ...

Expand Down Expand Up @@ -90,11 +86,5 @@ def _on_progress_scale_release(self, *_):
value = self.progress_scale.get_value()
self.emit("position-changed", value)

def _on_progress_key_pressed(self, _, event, *__):
if event in {Gdk.KEY_Up, Gdk.KEY_Left}:
self.emit("rewind")
elif event in {Gdk.KEY_Down, Gdk.KEY_Right}:
self.emit("forward")

def _on_progress_scale_press(self, *_):
self._progress_scale_pressed = True
16 changes: 16 additions & 0 deletions cozy/view_model/playback_control_view_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,25 @@ def play_pause(self):

def rewind(self):
self._player.rewind()
self._player._emit_tick()

def forward(self):
self._player.forward()
self._player._emit_tick()

def next_chapter(self):
self._player._next_chapter()

def previous_chapter(self):
self._player._previous_chapter()

def volume_up(self):
self._player.volume_up()
self._notify("volume")

def volume_down(self):
self._player.volume_down()
self._notify("volume")

def open_book_detail(self):
if self.book:
Expand Down
12 changes: 12 additions & 0 deletions cozy/view_model/playback_speed_view_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,15 @@ def _on_player_event(self, event: str, message):
if event == "chapter-changed" and message:
self._book = message
self._notify("playback_speed")

def speed_up(self):
self.playback_speed = min(self.playback_speed + 0.1, 3.5)
self._notify("playback_speed")

def speed_down(self):
self.playback_speed = max(self.playback_speed - 0.1, 0.5)
self._notify("playback_speed")

def speed_reset(self):
self.playback_speed = 1.0
self._notify("playback_speed")
1 change: 1 addition & 0 deletions data/ui/headerbar.blp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ template $Headerbar: Box {
tooltip-text: _("Options");
menu-model: primary_menu;
icon-name: 'open-menu-symbolic';
primary: true;

accessibility {
label: _("Open the options popover");
Expand Down

0 comments on commit e5190e1

Please sign in to comment.