diff --git a/Cargo.lock b/Cargo.lock index 844e722120234959bab0a55176f51718b74e97cb..5e1ef03a5e41eafebb6fa5b7e22f02bb3abc53d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,7 +1348,7 @@ dependencies = [ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1376,7 +1376,7 @@ dependencies = [ "protoc-rust 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1483,7 +1483,7 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1502,7 +1502,6 @@ dependencies = [ "gstreamer 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-pbutils 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "gtk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "libhandy 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mdns 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1513,6 +1512,7 @@ dependencies = [ "rusqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust_cast 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustio 0.0.1 (git+https://gitlab.gnome.org/haecker-felix/Rustio.git)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "stopwatch 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2071,7 +2071,7 @@ dependencies = [ "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "9f301d728f2b94c9a7691c90f07b0b4e8a4517181d9461be94c04bddeb4bd850" "checksum serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "beed18e6f5175aef3ba670e57c60ef3b1b74d250d962a26604bff4c80e970dd4" -"checksum serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "27dce848e7467aa0e2fcaf0a413641499c0b745452aaca1194d24dedde9e13c9" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" diff --git a/Cargo.toml b/Cargo.toml index b33c88edfbd441f2e452aef7ffe023f5d362a632..fd1aa927a650736f2352fab69656d43c2fa621f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" [dependencies] gtk = { version="0.6.0", features=["v3_22"] } libhandy = { version="0.3.0", features=["v0_0_7"] } -gio = "0.6.0" -glib = "0.7.0" +glib = { version="0.7.0", features=["subclassing"] } +gio = { version="0.6.0", features=["v2_46"] } gdk = "0.10.0" gstreamer = "0.13.0" gstreamer-pbutils = "0.13.0" @@ -18,7 +18,6 @@ mpris-player = { git="https://gitlab.gnome.org/World/Rust/mpris-player" } log = "0.4" pretty_env_logger = "0.3.0" rusqlite = "0.13" -indexmap = "1.0" quick-error = "1.2.2" restson = "0.4" stopwatch = "0.0.7" @@ -26,3 +25,4 @@ uuid = { version = "0.7", features = ["v4"] } chrono = "0.4.6" rust_cast = "0.14.0" mdns = "0.3.1" +serde_json = "1.0.39" diff --git a/de.haeckerfelix.ShortwaveDevel.json b/de.haeckerfelix.ShortwaveDevel.json index cb250cd08e271d6ada6acd1cd0c53cce09367687..6b6d653da83d8310e783dcbeb406757cdd8f4b6e 100644 --- a/de.haeckerfelix.ShortwaveDevel.json +++ b/de.haeckerfelix.ShortwaveDevel.json @@ -1,7 +1,7 @@ { "app-id" : "de.haeckerfelix.ShortwaveDevel", "runtime" : "org.gnome.Platform", - "runtime-version" : "master", + "runtime-version" : "3.30", "sdk" : "org.gnome.Sdk", "sdk-extensions" : [ "org.freedesktop.Sdk.Extension.rust-stable" diff --git a/src/library.rs b/src/library.rs index de725237ae05e2de6b0725882d3e7c4cb484e041..5c0fafa2f033ca1f3c56099c6c1da0c72e75385c 100644 --- a/src/library.rs +++ b/src/library.rs @@ -13,6 +13,7 @@ use std::thread; use crate::app::Action; use crate::config; +use crate::station_model::StationModel; use crate::station_model::{Order, Sorting}; use crate::widgets::station_listbox::StationListBox; use crate::widgets::station_row::ContentType; @@ -24,7 +25,7 @@ static SQL_INIT_COLLECTIONS: &str = " CREATE TABLE \"collections\" ('collection_ pub struct Library { pub widget: gtk::Box, - station_listbox: RefCell, + library_model: RefCell, db_path: PathBuf, builder: gtk::Builder, @@ -36,8 +37,11 @@ impl Library { let builder = gtk::Builder::new_from_resource("/de/haeckerfelix/Shortwave/gtk/library.ui"); let widget: gtk::Box = builder.get_object("library").unwrap(); let content_box: gtk::Box = builder.get_object("content_box").unwrap(); - let station_listbox = RefCell::new(StationListBox::new(sender.clone(), ContentType::Library)); - content_box.add(&station_listbox.borrow().widget); + + let library_model = RefCell::new(StationModel::new()); + let station_listbox = StationListBox::new(sender.clone(), ContentType::Library); + station_listbox.bind_model(&library_model.borrow()); + content_box.add(&station_listbox.widget); let db_path = Self::get_database_path("shortwave.db").expect("Could not open database path..."); @@ -48,7 +52,7 @@ impl Library { let library = Self { widget, - station_listbox, + library_model, db_path, builder, sender, @@ -63,19 +67,23 @@ impl Library { pub fn add_stations(&self, stations: Vec) { debug!("Add {} station(s)", stations.len()); - self.station_listbox.borrow_mut().add_stations(stations); + for station in stations { + self.library_model.borrow_mut().add_station(station.clone()); + } self.update_visible_page(); } pub fn remove_stations(&self, stations: Vec) { debug!("Remove {} station(s)", stations.len()); - self.station_listbox.borrow_mut().remove_stations(stations); + for station in stations { + self.library_model.borrow_mut().remove_station(station.clone()); + } self.update_visible_page(); } pub fn write_data(&self) { debug!("Write library data to disk..."); - Self::write_stations_to_db(&self.db_path, self.station_listbox.borrow().get_stations()).expect("Could not write stations to database."); + Self::write_stations_to_db(&self.db_path, self.library_model.borrow().clone()).expect("Could not write stations to database."); } pub fn import_from_path(&self, path: &PathBuf) -> Result<(), LibraryError> { @@ -100,12 +108,12 @@ impl Library { } pub fn export_to_path(&self, path: &PathBuf) -> Result<(), LibraryError> { - Self::write_stations_to_db(&path, self.station_listbox.borrow().get_stations()).expect("Could not export database."); + Self::write_stations_to_db(&path, self.library_model.borrow().clone()).expect("Could not export database."); Ok(()) } pub fn set_sorting(&self, sorting: Sorting, order: Order) { - self.station_listbox.borrow_mut().set_sorting(sorting, order); + self.library_model.borrow_mut().set_sorting(sorting, order); } fn read_stations_from_db(path: &PathBuf) -> Result, LibraryError> { @@ -131,7 +139,7 @@ impl Library { Ok(result) } - fn write_stations_to_db(path: &PathBuf, stations: Vec) -> Result<(), LibraryError> { + fn write_stations_to_db(path: &PathBuf, stations: StationModel) -> Result<(), LibraryError> { let tmpdb = Self::get_database_path("tmp.db")?; info!("Delete previous database data..."); @@ -186,7 +194,7 @@ impl Library { } fn update_visible_page(&self) { - if self.station_listbox.borrow().get_stations().len() != 0 { + if self.library_model.borrow().len() != 0 { self.set_visible_page("content"); } else { self.set_visible_page("empty"); @@ -224,7 +232,3 @@ quick_error! { } } } - -// TODO -// Lösung: Das Model/station_listbox einfach öffentlich (statisch) für alle module verfügbar machen -// ordentliches station model implementieren diff --git a/src/main.rs b/src/main.rs index 59ba6c428151256ced6bbfe5471a7d21f6c1df2e..f8abfe0f4ae5ab53f33a19d16104c853e5aa075b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,9 @@ extern crate pretty_env_logger; #[macro_use] extern crate quick_error; +#[macro_use] +extern crate glib; + mod app; mod config; mod library; @@ -13,6 +16,7 @@ mod search; mod song; mod static_resource; mod station_model; +mod station_object; mod widgets; mod window; diff --git a/src/search.rs b/src/search.rs index 4fc562daa6e665cb219c7066b23b639d32db75a0..0c14a53deb11df354f585f0b8111a39aa0c33ef8 100644 --- a/src/search.rs +++ b/src/search.rs @@ -5,12 +5,13 @@ use rustio::{Client, StationSearch}; use std::cell::RefCell; use crate::app::Action; +use crate::station_model::StationModel; use crate::widgets::station_listbox::StationListBox; use crate::widgets::station_row::ContentType; pub struct Search { pub widget: gtk::Box, - station_listbox: RefCell, + result_model: RefCell, builder: gtk::Builder, sender: Sender, @@ -21,13 +22,15 @@ impl Search { let builder = gtk::Builder::new_from_resource("/de/haeckerfelix/Shortwave/gtk/search.ui"); let widget: gtk::Box = builder.get_object("search").unwrap(); + let result_model = RefCell::new(StationModel::new()); let results_box: gtk::Box = builder.get_object("results_box").unwrap(); - let station_listbox = RefCell::new(StationListBox::new(sender.clone(), ContentType::Other)); - results_box.add(&station_listbox.borrow().widget); + let station_listbox = StationListBox::new(sender.clone(), ContentType::Other); + station_listbox.bind_model(&result_model.borrow()); + results_box.add(&station_listbox.widget); let search = Self { widget, - station_listbox, + result_model, builder, sender, }; @@ -42,8 +45,10 @@ impl Search { let mut client = Client::new("http://www.radio-browser.info"); let result = client.search(data).unwrap(); - self.station_listbox.borrow_mut().clear(); - self.station_listbox.borrow_mut().add_stations(result); + self.result_model.borrow_mut().clear(); + for station in result { + self.result_model.borrow_mut().add_station(station); + } } fn setup_signals(&self) { diff --git a/src/station_model.rs b/src/station_model.rs index cbf8366279947f42069b177e4553c04c5d2b2de6..11a1d1e6dfc20075c884de030cc7578ca0157f94 100644 --- a/src/station_model.rs +++ b/src/station_model.rs @@ -1,7 +1,10 @@ -use indexmap::map::Entry; -use indexmap::IndexMap; +use gio::prelude::ListStoreExtManual; +use gio::prelude::*; +use glib::prelude::*; use rustio::Station; +use crate::station_object::StationObject; + #[derive(Clone, Debug)] pub enum Sorting { Name, @@ -13,7 +16,7 @@ pub enum Sorting { Bitrate, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Order { Ascending, Descending, @@ -21,104 +24,97 @@ pub enum Order { #[derive(Clone, Debug)] pub struct StationModel { - map: IndexMap, + pub model: gio::ListStore, sorting: Sorting, order: Order, + + iter_id: u32, } impl StationModel { pub fn new() -> Self { - let map: IndexMap = IndexMap::new(); + let model = gio::ListStore::new(StationObject::static_type()); let sorting = Sorting::Name; let order = Order::Ascending; - Self { map, sorting, order } - } + let iter_id = 0; - pub fn export_vec(&self) -> Vec { - let mut result = Vec::new(); - for (_id, station) in self.map.clone() { - result.insert(0, station); - } - result + Self { model, sorting, order, iter_id } } - pub fn add_station(&mut self, station: Station) -> Option { - let mut index = None; - if !self.contains(&station) { - let id = station.id.parse::().unwrap(); - self.map.insert(id.clone(), station); - self.sort(); - index = match self.map.entry(id) { - Entry::Occupied(e) => Some(e.index()), - _ => None, - }; - } - index + pub fn add_station(&mut self, station: Station) { + let object = StationObject::new(station.clone()); + + let sorting = self.sorting.clone(); + let order = self.order.clone(); + self.model.insert_sorted(&object, move |a, b| Self::station_cmp(a, b, sorting.clone(), order.clone())); } - pub fn remove_station(&mut self, station: Station) -> Option { - let mut index = None; - if self.contains(&station) { - let id = station.id.parse::().unwrap(); - index = Some(self.map.swap_remove_full(&id).unwrap().0); - self.sort(); + pub fn remove_station(&mut self, station: Station) { + for i in 0..self.model.get_n_items() { + let gobject = self.model.get_object(i).unwrap(); + let station_object = gobject.downcast_ref::().expect("StationObject is of wrong type"); + let s = station_object.to_station(); + + if s == station { + self.model.remove(i); + break; + } } - index } - pub fn contains(&self, station: &Station) -> bool { - let id = station.id.parse::().unwrap(); - self.map.contains_key(&id) + pub fn set_sorting(&mut self, sorting: Sorting, order: Order) { + self.sorting = sorting.clone(); + self.order = order.clone(); + + self.model.sort(move |a, b| Self::station_cmp(a, b, sorting.clone(), order.clone())); } - pub fn set_sorting(&mut self, sorting: Sorting, order: Order) { - self.sorting = sorting; - self.order = order; + pub fn clear(&mut self) { + self.model.remove_all(); } - pub fn sort(&mut self) { - let order = self.order.clone(); - let sorting = self.sorting.clone(); + pub fn len(&self) -> u32 { + self.model.get_n_items() + } - self.map.sort_by(move |_, b, _, d| { - let station_a: Station; - let station_b: Station; - - match order { - Order::Ascending => { - station_a = b.clone(); - station_b = d.clone(); - } - Order::Descending => { - station_b = b.clone(); - station_a = d.clone(); - } - } + fn station_cmp(a: &gio::Object, b: &gio::Object, sorting: Sorting, order: Order) -> std::cmp::Ordering { + let mut station_a: Station = a.downcast_ref::().unwrap().to_station(); + let mut station_b: Station = b.downcast_ref::().unwrap().to_station(); - match sorting { - Sorting::Name => station_a.name.cmp(&station_b.name), - Sorting::Language => station_a.language.cmp(&station_b.language), - Sorting::Country => station_a.country.cmp(&station_b.country), - Sorting::State => station_a.state.cmp(&station_b.state), - Sorting::Codec => station_a.codec.cmp(&station_b.codec), - Sorting::Votes => station_a.votes.parse::().unwrap().cmp(&station_b.votes.parse::().unwrap()), - Sorting::Bitrate => station_a.bitrate.parse::().unwrap().cmp(&station_b.bitrate.parse::().unwrap()), - } - }); - } + if order == Order::Descending { + let tmp = station_a; + station_a = station_b; + station_b = tmp; + } - pub fn clear(&mut self) { - self.map.clear(); + match sorting { + Sorting::Name => station_a.name.cmp(&station_b.name), + Sorting::Language => station_a.language.cmp(&station_b.language), + Sorting::Country => station_a.country.cmp(&station_b.country), + Sorting::State => station_a.state.cmp(&station_b.state), + Sorting::Codec => station_a.codec.cmp(&station_b.codec), + Sorting::Votes => station_a.votes.parse::().unwrap().cmp(&station_b.votes.parse::().unwrap()), + Sorting::Bitrate => station_a.bitrate.parse::().unwrap().cmp(&station_b.bitrate.parse::().unwrap()), + } } } -impl IntoIterator for StationModel { - type Item = (u32, Station); - type IntoIter = ::indexmap::map::IntoIter; +impl Iterator for StationModel { + type Item = Station; + + fn next(&mut self) -> Option { + let max = self.len(); + let mut result = None; - fn into_iter(self) -> Self::IntoIter { - self.map.into_iter() + if self.iter_id < max { + let gobject = self.model.get_object(self.iter_id).unwrap(); + let station_object = gobject.downcast_ref::().expect("StationObject is of wrong type"); + result = Some(station_object.to_station()); + self.iter_id = self.iter_id + 1; + } + + result } } diff --git a/src/station_object.rs b/src/station_object.rs new file mode 100644 index 0000000000000000000000000000000000000000..34bf8ae7d4b88ca841d4eb4c65a5df4be3eeef67 --- /dev/null +++ b/src/station_object.rs @@ -0,0 +1,96 @@ +// StationObject is a GObject subclass, which we need to carry the rustio::Station struct. +// With this we can use gtk::ListBox bind_model() properly. +// +// For more details, you should look at this gtk-rs example: +// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs + +use super::*; +use gtk::prelude::*; +use rustio::Station; + +use glib::subclass; +use glib::subclass::prelude::*; +use glib::translate::*; + +mod imp { + use super::*; + use std::cell::RefCell; + + pub struct StationObject { + data: RefCell>, + } + + static PROPERTIES: [subclass::Property; 1] = [subclass::Property("data", |name| { + glib::ParamSpec::string( + name, + "Data", + "Data", + None, // Default value + glib::ParamFlags::READWRITE, + ) + })]; + + impl ObjectSubclass for StationObject { + const NAME: &'static str = "StationObject"; + type ParentType = glib::Object; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.install_properties(&PROPERTIES); + } + + fn new() -> Self { + Self { data: RefCell::new(None) } + } + } + + impl ObjectImpl for StationObject { + glib_object_impl!(); + + fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("data", ..) => { + let data = value.get(); + self.data.replace(data); + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("data", ..) => Ok(self.data.borrow().to_value()), + _ => unimplemented!(), + } + } + } +} + +glib_wrapper! { + pub struct StationObject(Object, subclass::simple::ClassStruct, StationObjectClass>); + + match fn { + get_type => || imp::StationObject::get_type().to_glib(), + } +} + +impl StationObject { + pub fn new(station: Station) -> StationObject { + glib::Object::new(Self::static_type(), &[("data", &serde_json::to_string(&station).unwrap())]) + .unwrap() + .downcast() + .unwrap() + } + + pub fn to_station(&self) -> Station { + let data = self.get_property("data").unwrap().get::().unwrap(); + serde_json::from_str(&data).unwrap() + } +} diff --git a/src/widgets/song_row.rs b/src/widgets/song_row.rs index 4047a1c04b1d21f207e7154e6628d4700bf3da82..4dc6257e22b60937c2f1771a37bdee818aae2076 100644 --- a/src/widgets/song_row.rs +++ b/src/widgets/song_row.rs @@ -14,7 +14,7 @@ impl SongRow { pub fn new(song: Song) -> Self { let widget = ActionRow::new(); widget.set_title(&song.title); - widget.set_subtitle(&Self::format_duration(song.duration.elapsed().as_secs())); //TODO: Display time correctly + widget.set_subtitle(&Self::format_duration(song.duration.elapsed().as_secs())); widget.set_icon_name(""); let save_button = gtk::Button::new(); diff --git a/src/widgets/station_listbox.rs b/src/widgets/station_listbox.rs index 4213773af579f17a7fc2e5e2be2be2a6ef7b8108..eea15a50821281ce276c207a62e228082b25844a 100644 --- a/src/widgets/station_listbox.rs +++ b/src/widgets/station_listbox.rs @@ -1,16 +1,15 @@ use glib::Sender; use gtk::prelude::*; use libhandy::{Column, ColumnExt}; -use rustio::Station; use crate::app::Action; -use crate::station_model::{Order, Sorting, StationModel}; +use crate::station_model::StationModel; +use crate::station_object::StationObject; use crate::widgets::station_row::{ContentType, StationRow}; pub struct StationListBox { pub widget: gtk::Box, listbox: gtk::ListBox, - station_model: StationModel, content_type: ContentType, sender: Sender, @@ -21,7 +20,6 @@ impl StationListBox { let builder = gtk::Builder::new_from_resource("/de/haeckerfelix/Shortwave/gtk/station_listbox.ui"); let widget: gtk::Box = builder.get_object("station_listbox").unwrap(); let listbox: gtk::ListBox = builder.get_object("listbox").unwrap(); - let station_model = StationModel::new(); // Setup HdyColumn let column = Column::new(); @@ -35,66 +33,18 @@ impl StationListBox { Self { widget, listbox, - station_model, content_type, sender, } } - pub fn add_stations(&mut self, stations: Vec) { - for station in stations { - match self.station_model.add_station(station.clone()) { - Some(index) => { - let row = StationRow::new(self.sender.clone(), station, self.content_type.clone()); - self.listbox.insert(&row.widget, index as i32); - } - None => (), - } - } - } - - pub fn remove_stations(&mut self, stations: Vec) { - for station in stations { - match self.station_model.remove_station(station) { - Some(index) => { - let row = self.listbox.get_row_at_index(index as i32).unwrap(); - self.listbox.remove(&row); - } - None => (), - } - } - } - - pub fn get_stations(&self) -> Vec { - self.station_model.export_vec() - } - - pub fn set_sorting(&mut self, sorting: Sorting, order: Order) { - self.station_model.set_sorting(sorting, order); - self.station_model.sort(); - self.refresh(); - } - - fn refresh(&self) { - // remove all rows - for widget in self.listbox.get_children() { - widget.destroy(); - } - - // sort - for (_, station) in self.station_model.clone() { - let row = StationRow::new(self.sender.clone(), station, self.content_type.clone()); - self.listbox.add(&row.widget); - } - } - - pub fn clear(&mut self) { - // remove all rows - for widget in self.listbox.get_children() { - widget.destroy(); - } + pub fn bind_model(&self, model: &StationModel) { + let sender = self.sender.clone(); + let content_type = self.content_type.clone(); - // clear station_model - self.station_model.clear(); + self.listbox.bind_model(&model.model, move |station| { + let row = StationRow::new(sender.clone(), station.downcast_ref::().unwrap().to_station(), content_type.clone()); + row.widget.upcast::() + }); } }