[GtkTemplate (ui = "/eu/callcc/audrey/ui/play_queue.ui")] public class Ui.PlayQueue : Adw.NavigationPage { public PlayQueueSelection selection { get; default = new PlayQueueSelection (); } private GLib.ListStore _store; public GLib.ListStore store { get { return _store; } set { // can only be set once assert (_store == null); _store = value; _store.items_changed.connect (this.on_store_items_changed); this.can_clear_all = _store.get_n_items () > 0; this.selection.model = _store; } } public bool can_clear_all { get; private set; } [GtkCallback] private void on_clear () { this.store.remove_all (); } private void on_store_items_changed (GLib.ListModel store, uint position, uint removed, uint added) { this.can_clear_all = store.get_n_items () > 0; } } // this is a custom SelectionModel that lets us only signal the // selection has changed on user interaction public class PlayQueueSelection : GLib.Object, GLib.ListModel, Gtk.SelectionModel { private GLib.ListModel _model; public GLib.ListModel model { get { return _model; } set { // can only be set once assert (_model == null); _model = value; _model.items_changed.connect (this.on_model_items_changed); } } // if current_position == model.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); // only called by playbin // does not emit user_selected internal void playbin_select (uint new_position) { var previous_position = this.current_position; this.current_position = new_position; if (previous_position < this.get_n_items ()) { if (new_position < this.get_n_items ()) { if (previous_position < new_position) { this.selection_changed (previous_position, new_position-previous_position+1); } else { this.selection_changed (new_position, previous_position-new_position+1); } } else { this.selection_changed (previous_position, 1); } } else { if (new_position < this.get_n_items ()) { this.selection_changed (new_position, 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 ()) { this.user_selected (position); } private void on_model_items_changed (GLib.ListModel model, 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.get_n_items ()) { var bitset = new Gtk.Bitset.empty (); bitset.add (this.current_position); return bitset; } bool is_selected (uint position) requires (position < this.get_n_items ()) { return position == this.current_position; } bool select_all () { return false; } bool select_item (uint position, bool unselect_rest) requires (position < this.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.ListModel methods Object? get_item (uint position) { if (this.model == null) return null; return this.model.get_item (position); } Type get_item_type () { return this.model.get_item_type (); } uint get_n_items () { if (this.model == null) return 0; return this.model.get_n_items (); } }