class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel { public ListStore inner = new ListStore (typeof (Song)); public uint playing_index { get; private set; default = 0; } public signal void begin_playback (Song song); public signal void prepare_next (Song? song); public signal void playback_continues (Song song); public signal void stop_playback (); construct { this.inner.items_changed.connect ((position, removed, added) => { if (this.playing_index >= position) { if (this.playing_index < position+removed) { this.playing_index = position; if (this.playing_index < this.inner.get_n_items ()) { this.begin_playback ((Song) this.inner.get_item (this.playing_index)); this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1)); } else { this.stop_playback (); } } else { this.playing_index += added; this.playing_index -= removed; } } else { this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1)); } this.items_changed (position, removed, added); if (this.playing_index < this.inner.get_n_items ()) { this.selection_changed (this.playing_index, 1); } }); } public void song_transition () { this.playing_index += 1; this.selection_changed (this.playing_index-1, 2); this.playback_continues ((Song) this.inner.get_item (this.playing_index)); this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1)); } public void playback_finished () { this.playing_index += 1; assert (this.playing_index == this.inner.get_n_items ()); this.selection_changed (this.playing_index-1, 1); } Gtk.Bitset get_selection_in_range (uint position, uint n_items) { var bitset = new Gtk.Bitset.empty (); if (this.playing_index < this.inner.get_n_items ()) { bitset.add (playing_index); } return bitset; } bool is_selected (uint position) { return position == this.playing_index; } bool select_all () { return false; } bool select_item (uint position, bool unselect_rest) { if (!unselect_rest) { return false; } var previous = this.playing_index; this.playing_index = position; if (previous < this.inner.get_n_items ()) { this.selection_changed (previous, 1); } this.selection_changed (position, 1); this.begin_playback ((Song) this.inner.get_item (this.playing_index)); this.prepare_next ((Song) this.inner.get_item (this.playing_index+1)); 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; PlayQueueStore store = new PlayQueueStore (); public signal void begin_playback (Song song); public signal void prepare_next (Song? next_song); public signal void playback_continues (Song song); public signal void stop_playback (); public bool can_clear_all { get; private set; default = false; } construct { this.view.model = this.store; } [GtkCallback] public void clear () { this.store.inner.remove_all (); this.can_clear_all = false; this.store.begin_playback.connect ((song) => this.begin_playback (song)); this.store.prepare_next.connect ((next_song) => this.prepare_next (next_song)); this.store.playback_continues.connect ((song) => this.playback_continues (song)); this.store.stop_playback.connect (() => this.stop_playback ()); } public void queue (Song song) { this.store.inner.append (song); } internal void song_transition () { this.store.song_transition (); } internal void playback_finished () { this.store.playback_finished (); } internal void restart () { this.store.select_item (0, true); } public void skip_forward () { this.store.select_item (this.store.playing_index+1, true); } public void skip_backward () { if (this.store.playing_index >= 1) { this.store.select_item (this.store.playing_index-1, true); } } }