From de5c1179fa1a05a4396d120e6828ec64c17334d8 Mon Sep 17 00:00:00 2001 From: Erica Z Date: Wed, 16 Oct 2024 10:48:52 +0200 Subject: [PATCH] extract playqueue model outside ui --- src/meson.build | 1 + src/play_queue.vala | 152 +++++++++++++++++++++++++++++++++++++++++ src/playbin.vala | 5 +- src/ui/play_queue.vala | 105 ---------------------------- src/ui/window.vala | 12 ++-- 5 files changed, 163 insertions(+), 112 deletions(-) create mode 100644 src/play_queue.vala diff --git a/src/meson.build b/src/meson.build index b6dd941..d986e5a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,6 +4,7 @@ audrey_sources = [ 'globalconf.vala', 'main.vala', 'mpris.vala', + 'play_queue.vala', 'playbin.vala', 'ui/play_queue.vala', 'ui/setup.vala', diff --git a/src/play_queue.vala b/src/play_queue.vala new file mode 100644 index 0000000..1e49892 --- /dev/null +++ b/src/play_queue.vala @@ -0,0 +1,152 @@ +// this is a custom SelectionModel that lets us only signal the +// selection has changed on user interaction +internal class Audrey.PlayQueue : GLib.Object, GLib.ListModel, Gtk.SelectionModel { + public GLib.ListStore inner { get; private set; } + // if current_position == inner.get_n_items (), play queue is stopped + public uint current_position { get; private set; } + + // emitted when a track is purposefully selected + public signal void user_selected (uint position); + + public PlayQueue () { + this.inner = new GLib.ListStore (typeof (Song)); + this.current_position = this.inner.get_n_items (); + + this.inner.items_changed.connect (this.on_inner_items_changed); + } + + // only called by playbin on track transition + // does not emit user_selected + internal void playbin_advance (uint new_position) + requires (this.current_position < this.get_n_items ()) + requires (this.current_position+1 == new_position) + ensures (this.current_position == new_position) + { + this.current_position += 1; + if (this.current_position < this.inner.get_n_items ()) { + // previous track unselected, this newly selected + this.selection_changed (this.current_position-1, 2); + } else { + // end of play queue reached + // only previous track unselected + this.selection_changed (this.current_position-1, 1); + } + } + + // called by anything else that wishes to switch tracks + // emits user_selected + public void user_select (uint position) + requires (position <= this.get_n_items ()) + { + var previous_position = this.current_position; + this.current_position = position; + this.user_selected (position); + + if (previous_position == this.get_n_items ()) { + // nothing selected before + if (position == this.get_n_items ()) { + // ...nothing selected after + } else { + // select new + this.selection_changed (position, 1); + } + } else { + if (position == this.get_n_items ()) { + // unselect previous + this.selection_changed (previous_position, 1); + } else if (position < previous_position) { + this.selection_changed (position, previous_position-position+1); + } else { + this.selection_changed (previous_position, position-previous_position+1); + } + } + } + + private void on_inner_items_changed (GLib.ListModel inner, uint position, uint removed, uint added) { + // FIXME: potentially try to see if the current item was reordered + // see what Gtk.SingleSelection does + + bool emit_signal = false; + + if (this.current_position >= position) { + if (this.current_position < position+removed && added == 0) { + emit_signal = true; + this.current_position = position; + } else { + this.current_position += added; + this.current_position -= removed; + } + } + + this.items_changed (position, removed, added); + if (emit_signal) this.selection_changed (position, 1); + // user_select doesnt need to be signalled, since the playqueue + // handles this on its own + } + + // Gtk.SelectionModel methods + + Gtk.Bitset get_selection_in_range (uint position, uint n_items) + requires (position+n_items <= this.inner.get_n_items ()) + { + var bitset = new Gtk.Bitset.empty (); + bitset.add (this.current_position); + return bitset; + } + + bool is_selected (uint position) + requires (position < this.inner.get_n_items ()) + { + return position == this.current_position; + } + + bool select_all () { + return false; + } + + bool select_item (uint position, bool unselect_rest) + requires (position < this.inner.get_n_items ()) + { + if (!unselect_rest) { + return false; + } + + this.user_select (position); + + return true; + } + + bool select_range (uint position, uint n_items, bool unselect_rest) { + return false; + } + + bool set_selection (Gtk.Bitset selected, Gtk.Bitset mask) { + return false; + } + + bool unselect_all () { + return false; + } + + bool unselect_item (uint position) { + return false; + } + + bool unselect_range (uint position, uint n_items) { + return false; + } + + // GLib.ListView methods + + Object? get_item (uint position) { + return this.inner.get_item (position); + } + + Type get_item_type () { + return this.inner.get_item_type (); + } + + uint get_n_items () { + return this.inner.get_n_items (); + } +} diff --git a/src/playbin.vala b/src/playbin.vala index 18f3250..741b08c 100644 --- a/src/playbin.vala +++ b/src/playbin.vala @@ -32,7 +32,7 @@ class Playbin : GLib.Object { public Subsonic api { get; set; default = null; } // sent when a new song starts playing - public signal void now_playing (uint index, Song song, int64 duration); + public signal void now_playing (bool continues, uint index, Song song, int64 duration); // FIXME this should be synced with the selection model, right?? public uint playing_index { get; private set; } @@ -162,6 +162,7 @@ class Playbin : GLib.Object { this.position = 0; + bool continues = this.next_gapless; if (this.next_gapless) { // advance position in play queue this.playing_index += 1; @@ -171,7 +172,7 @@ class Playbin : GLib.Object { var now_playing = (Song) play_queue.get_item (this.playing_index); if (this.api.stream_uri (now_playing.id) == (string) this.playbin.current_uri) { - this.now_playing (this.playing_index, now_playing, duration); + this.now_playing (continues, this.playing_index, now_playing, duration); if (this.playing_index+1 < play_queue.get_n_items ()) { Song song = (Song) play_queue.get_item (this.playing_index+1); diff --git a/src/ui/play_queue.vala b/src/ui/play_queue.vala index b74024e..77a54e4 100644 --- a/src/ui/play_queue.vala +++ b/src/ui/play_queue.vala @@ -1,108 +1,3 @@ -// this is a custom SelectionModel that lets us only signal the -// selection has changed on user interaction -class PlayQueueSelection : GLib.Object, GLib.ListModel, Gtk.SelectionModel { - public ListStore inner { get; private set; } - - private uint _selected_position; - public uint selected_position { - get { return _selected_position; } - set { - var previous = _selected_position; - _selected_position = value; - - if (previous < inner.get_n_items ()) { - this.selection_changed (previous, 1); - } - if (value < inner.get_n_items ()) { - this.selection_changed (value, 1); - } - } - } - - public signal void user_selected (uint position); - - internal PlayQueueSelection () { - this.inner = new GLib.ListStore (typeof (Song)); - this._selected_position = inner.get_n_items (); - - this.inner.items_changed.connect ((position, removed, added) => { - bool emit_signal = false; - - if (this.selected_position >= position) { - if (this.selected_position < position+removed && added == 0) { - emit_signal = true; - this._selected_position = position; - } else { - this._selected_position += added; - this._selected_position -= removed; - } - } - - this.items_changed (position, removed, added); - if (emit_signal) this.selection_changed (position, 1); - }); - } - - Gtk.Bitset get_selection_in_range (uint position, uint n_items) { - var bitset = new Gtk.Bitset.empty (); - if (this.selected_position < this.inner.get_n_items ()) { - bitset.add (selected_position); - } - return bitset; - } - - bool is_selected (uint position) { - return position == this.selected_position; - } - - bool select_all () { - return false; - } - - bool select_item (uint position, bool unselect_rest) { - if (!unselect_rest) { - return false; - } - - this.selected_position = position; - this.user_selected (position); - - return true; - } - - bool select_range (uint position, uint n_items, bool unselect_rest) { - return false; - } - - bool set_selection (Gtk.Bitset selected, Gtk.Bitset mask) { - return false; - } - - bool unselect_all () { - return false; - } - - bool unselect_item (uint position) { - return false; - } - - bool unselect_range (uint position, uint n_items) { - return false; - } - - Object? get_item (uint position) { - return this.inner.get_item (position); - } - - Type get_item_type () { - return this.inner.get_item_type (); - } - - uint get_n_items () { - return this.inner.get_n_items (); - } -} - [GtkTemplate (ui = "/eu/callcc/audrey/ui/play_queue.ui")] public class Ui.PlayQueue : Adw.NavigationPage { [GtkChild] private unowned Gtk.ColumnView view; diff --git a/src/ui/window.vala b/src/ui/window.vala index 4db5fda..a70f8ed 100644 --- a/src/ui/window.vala +++ b/src/ui/window.vala @@ -28,7 +28,7 @@ class Ui.Window : Adw.ApplicationWindow { public Gdk.Paintable playing_cover_art { get; set; } public Playbin playbin { get; private set; default = new Playbin (); } - public PlayQueueSelection play_queue_model { get; private set; default = new PlayQueueSelection (); } + public Audrey.PlayQueue play_queue_model { get; private set; default = new Audrey.PlayQueue (); } public Window (Gtk.Application app) { Object (application: app); @@ -61,11 +61,13 @@ class Ui.Window : Adw.ApplicationWindow { this.setup.connected.connect ((api) => { this.playbin.api = api; - this.playbin.now_playing.connect ((position, song, duration) => { + this.playbin.now_playing.connect ((continues, position, song, duration) => { this.song = song; this.duration = duration; api.scrobble.begin (song.id); - this.play_queue_model.selected_position = position; + if (continues) { + this.play_queue_model.playbin_advance (position); + } }); this.play_queue_model.user_selected.connect ((position) => { @@ -189,12 +191,12 @@ class Ui.Window : Adw.ApplicationWindow { } [GtkCallback] private void on_skip_forward_clicked () { - this.play_queue_model.select_item (this.playbin.playing_index+1, true); + this.play_queue_model.user_select (this.playbin.playing_index+1); } [GtkCallback] private void on_skip_backward_clicked () { if (this.playbin.playing_index > 0) { - this.play_queue_model.select_item (this.playbin.playing_index-1, true); + this.play_queue_model.user_select (this.playbin.playing_index-1); } }