Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add common keyboard controls #947

Merged
merged 21 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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