diff --git a/src/backend/manager.rs b/src/backend/manager.rs index 7fda78c..02c8a31 100644 --- a/src/backend/manager.rs +++ b/src/backend/manager.rs @@ -18,28 +18,42 @@ use crate::{ ui::PwvucontrolWindowView, backend::pwnodemodel::PwNodeModel, backend::pwdeviceobject::PwDeviceObject, - backend::pwnodeobject, ui::PwvucontrolWindow, PwvucontrolApplication, - backend::NodeType + backend::NodeType, + backend::PwNodeFilterModel }; use crate::macros::*; use once_cell::unsync::OnceCell; mod imp { + use super::*; - #[derive(Default, Properties)] + #[derive(Properties)] #[properties(wrapper_type = super::PwvucontrolManager)] pub struct PwvucontrolManager { #[property(get)] pub wp_core: OnceCell, pub wp_object_manager: OnceCell, - pub nodemodel: PwNodeModel, - pub sourcemodel: PwNodeModel, - pub sinkmodel: PwNodeModel, - pub devicemodel: OnceCell, + #[property(get)] + pub(crate) node_model: PwNodeModel, + + #[property(get)] + pub(crate) stream_output_model: PwNodeFilterModel, + + #[property(get)] + pub(crate) stream_input_model: PwNodeFilterModel, + + #[property(get)] + pub(crate) source_model: PwNodeFilterModel, + + #[property(get)] + pub(crate) sink_model: PwNodeFilterModel, + + #[property(get)] + pub(crate) device_model: gio::ListStore, pub metadata_om: OnceCell, pub metadata: RefCell>, @@ -53,6 +67,26 @@ mod imp { application: RefCell>, } + impl Default for PwvucontrolManager { + fn default() -> Self { + Self { + wp_core: Default::default(), + wp_object_manager: Default::default(), + node_model: Default::default(), + stream_input_model: PwNodeFilterModel::new(NodeType::StreamInput, None::), + stream_output_model: PwNodeFilterModel::new(NodeType::StreamOutput, None::), + source_model: PwNodeFilterModel::new(NodeType::Source, None::), + sink_model: PwNodeFilterModel::new(NodeType::Sink, None::), + device_model: gio::ListStore::new::(), + metadata_om: Default::default(), + metadata: Default::default(), + default_nodes_api: Default::default(), + mixer_api: Default::default(), + application: Default::default(), + } + } + } + #[glib::object_subclass] impl ObjectSubclass for PwvucontrolManager { const NAME: &'static str = "PwvucontrolManager"; @@ -63,7 +97,11 @@ mod imp { impl ObjectImpl for PwvucontrolManager { fn constructed(&self) { self.parent_constructed(); - self.devicemodel.set(gio::ListStore::new::()).expect("devicemodel not set"); + + self.stream_input_model.set_model(Some(self.node_model.clone())); + self.stream_output_model.set_model(Some(self.node_model.clone())); + self.sink_model.set_model(Some(self.node_model.clone())); + self.source_model.set_model(Some(self.node_model.clone())); self.setup_wp_connection(); self.setup_metadata_om(); @@ -151,7 +189,7 @@ mod imp { wp_om.connect_object_added( clone!(@weak self as imp, @weak wp_core as core => move |_, object| { - let devicemodel = imp.devicemodel.get().expect("devicemodel"); + let devicemodel = &imp.device_model; if let Some(node) = object.dynamic_cast_ref::() { // Hide ourselves if node.name() == Some("pwvucontrol-peak-detect".to_string()) { @@ -173,11 +211,7 @@ mod imp { } pwvucontrol_info!("Got node: {} bound id {}", node.name().unwrap_or_default(), node.bound_id()); let pwobj = PwNodeObject::new(node); - let model = match pwobj.nodetype() { - NodeType::Sink => &imp.sinkmodel, - NodeType::Source => &imp.sourcemodel, - _ => &imp.nodemodel - }; + let model = &imp.node_model; model.append(&pwobj); } else if let Some(device) = object.dynamic_cast_ref::() { let n: String = device.pw_property("device.name").unwrap(); @@ -190,14 +224,10 @@ mod imp { ); wp_om.connect_object_removed(clone!(@weak self as imp => move |_, object| { - let devicemodel = imp.devicemodel.get().expect("devicemodel"); + let devicemodel = &imp.device_model; if let Some(node) = object.dynamic_cast_ref::() { pwvucontrol_info!("removed: {:?} id: {}", node.name(), node.bound_id()); - let model = match pwnodeobject::get_node_type_for_node(node) { - NodeType::Sink => &imp.sinkmodel, - NodeType::Source => &imp.sourcemodel, - _ => &imp.nodemodel - }; + let model = &imp.node_model; model.remove(node.bound_id()); } else if let Some(device) = object.dynamic_cast_ref::() { @@ -303,7 +333,7 @@ impl PwvucontrolManager { } pub fn get_device_by_id(&self, id: u32) -> Option { - let devicemodel = self.imp().devicemodel.get().expect("devicemodel"); + let devicemodel = &self.imp().device_model; for device in devicemodel.iter::() { if let Ok(device) = device { if device.wpdevice().bound_id() == id { @@ -313,6 +343,18 @@ impl PwvucontrolManager { } None } + + pub fn get_node_by_id(&self, id: u32) -> Option { + let nodemodel = &self.imp().node_model; + for node in nodemodel.iter::() { + if let Ok(node) = node { + if node.wpnode().bound_id() == id { + return Some(node); + } + } + } + None + } } impl Default for PwvucontrolManager { diff --git a/src/backend/mod.rs b/src/backend/mod.rs index f866ed2..e5da25f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -8,6 +8,7 @@ mod pwnodeobject; mod pwrouteobject; mod routedirection; mod pwroutefiltermodel; +mod pwnodefiltermodel; pub use paramavailability::ParamAvailability; pub use pwchannelobject::PwChannelObject; @@ -18,4 +19,5 @@ pub use pwnodemodel::PwNodeModel; pub use pwnodeobject::{PwNodeObject, NodeType}; pub use pwrouteobject::PwRouteObject; pub use routedirection::RouteDirection; -pub use pwroutefiltermodel::PwRouteFilterModel; \ No newline at end of file +pub use pwroutefiltermodel::PwRouteFilterModel; +pub use pwnodefiltermodel::PwNodeFilterModel; diff --git a/src/backend/pwnodefiltermodel.rs b/src/backend/pwnodefiltermodel.rs new file mode 100644 index 0000000..d64b1b3 --- /dev/null +++ b/src/backend/pwnodefiltermodel.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +use glib::{closure_local, subclass::prelude::*, Properties}; +use gtk::{gio, prelude::*, subclass::prelude::*}; +use std::cell::{Cell, RefCell, OnceCell}; +use super::{NodeType, PwNodeObject}; + +mod imp { + use glib::SignalHandlerId; + + use crate::backend::PwNodeObject; + + use super::*; + + #[derive(Debug, Properties, Default)] + #[properties(wrapper_type = super::PwNodeFilterModel)] + pub struct PwNodeFilterModel { + /// Contains the items that matches the filter predicate. + pub(super) filtered_model: OnceCell, + + #[property(get, set, construct_only, builder(NodeType::Undefined))] + pub(super) nodetype: Cell, + + /// The model we are filtering. + #[property(get, set = Self::set_model, nullable)] + pub(super) model: RefCell>, + + pub(crate) signalid: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for PwNodeFilterModel { + const NAME: &'static str = "PwNodeFilterModel"; + type Type = super::PwNodeFilterModel; + type Interfaces = (gio::ListModel,); + } + + #[glib::derived_properties] + impl ObjectImpl for PwNodeFilterModel { + fn constructed(&self) { + self.parent_constructed(); + + let nodetype = self.nodetype.get(); + + let filter = gtk::CustomFilter::new(move |obj| { + let node: &PwNodeObject = obj.downcast_ref().expect("PwNodeObject"); + node.nodetype() == nodetype + }); + + self.filtered_model.set(gtk::FilterListModel::new(None::, Some(filter))).expect("filtered model not set"); + + } + } + + impl ListModelImpl for PwNodeFilterModel { + fn item_type(&self) -> glib::Type { + PwNodeObject::static_type() + } + fn n_items(&self) -> u32 { + self.filtered_model.get().expect("Filtered model").n_items() + } + fn item(&self, position: u32) -> Option { + self.filtered_model.get().expect("Filtered model").item(position) + } + } + + impl PwNodeFilterModel { + pub fn set_model(&self, new_model: Option) { + let filtered_model = self.filtered_model.get().expect("Filtered model"); + let removed = filtered_model.n_items(); + let widget = self.obj(); + + self.disconnect(); + + if let Some(new_model) = new_model { + + assert!(self.item_type().is_a(new_model.item_type())); + + let handler = closure_local!(@watch widget => move |_listmodel: &gio::ListModel, position: u32, removed: u32, added: u32| { + widget.items_changed(position, removed, added); + }); + //handler.invoke::<()>(&[&new_model, &0u32, &0u32, &0u32]); + self.signalid.replace(Some(filtered_model.connect_closure("items-changed", true, handler))); + + filtered_model.set_model(Some(&new_model)); + + self.model.replace(Some(new_model)); + } else { + widget.items_changed(0, removed, 0); + } + } + + pub fn disconnect(&self) { + let filtered_model = self.filtered_model.get().expect("Filtered model"); + filtered_model.set_model(gio::ListModel::NONE); + if let Some(id) = self.signalid.take() { + filtered_model.disconnect(id); + } + } + } +} + +glib::wrapper! { + pub struct PwNodeFilterModel(ObjectSubclass) @implements gio::ListModel; +} + +impl PwNodeFilterModel { + pub(crate) fn new(nodetype: NodeType, model: Option>) -> Self + { + glib::Object::builder() + .property("model", &model) + .property("nodetype", nodetype) + .build() + } + + pub fn get_node_pos_from_id(&self, id: u32) -> Option { + let pos: Option = self.iter::().position(|item| { + item.map_or(false, |item| item.boundid() == id) + }); + pos.map(|x| x as u32) + } +} diff --git a/src/backend/pwnodeobject.rs b/src/backend/pwnodeobject.rs index 8143931..d73f8d0 100644 --- a/src/backend/pwnodeobject.rs +++ b/src/backend/pwnodeobject.rs @@ -520,7 +520,7 @@ impl PwNodeObject { target_serial.as_str(), true), ].iter().collect::>()) { - return manager.imp().sinkmodel.get_node(sinknode.bound_id()).ok(); + return manager.get_node_by_id(sinknode.bound_id()); }; } } @@ -533,7 +533,7 @@ impl PwNodeObject { target_node.as_str(), true), ].iter().collect::>()) { - return manager.imp().sinkmodel.get_node(sinknode.bound_id()).ok(); + return manager.get_node_by_id(sinknode.bound_id()); }; } } diff --git a/src/ui/output_dropdown.rs b/src/ui/output_dropdown.rs index 1e7ee7f..e976cda 100644 --- a/src/ui/output_dropdown.rs +++ b/src/ui/output_dropdown.rs @@ -99,7 +99,7 @@ mod imp { self.outputdevice_dropdown.set_factory(Some(&factory)); self.outputdevice_dropdown.set_list_factory(default_dropdown_factory.as_ref()); - let sinkmodel = &manager.imp().sinkmodel; + let sinkmodel = &manager.sink_model(); self.outputdevice_dropdown.set_enable_search(true); diff --git a/src/ui/outputbox.rs b/src/ui/outputbox.rs index 5ac1bb9..d5ae575 100644 --- a/src/ui/outputbox.rs +++ b/src/ui/outputbox.rs @@ -102,7 +102,7 @@ impl PwOutputBox { pub(crate) fn update_output_device_dropdown(&self) { let manager = PwvucontrolManager::default(); - let sinkmodel = &manager.imp().sinkmodel; + let sinkmodel = manager.sink_model(); let imp = self.imp(); @@ -110,7 +110,7 @@ impl PwOutputBox { let id = self.default_node(); - let string = if let Ok(node) = sinkmodel.get_node(id) { + let string = if let Some(node) = manager.get_node_by_id(id) { format!("Default ({})", node.name()) } else { "Default".to_string() diff --git a/src/ui/window.rs b/src/ui/window.rs index 601ab5a..a2736db 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -12,7 +12,6 @@ use crate::{ application::PwvucontrolApplication, ui::PwVolumeBox, backend::PwNodeObject, - backend::NodeType, ui::PwSinkBox, ui::PwOutputBox, config::{APP_ID, PROFILE} @@ -102,23 +101,10 @@ mod imp { self.obj().setup_scroll_blocker(&self.outputlist); let manager = PwvucontrolManager::default(); - let model = &manager.imp().nodemodel; - let sourcemodel = &manager.imp().sourcemodel; - let sinkmodel = &manager.imp().sinkmodel; - let devicemodel = manager.imp().devicemodel.get().expect("Device model"); - let window = self; - - let filter = gtk::CustomFilter::new(|x| { - if let Some(o) = x.downcast_ref::() { - return o.nodetype() == NodeType::StreamOutput; - } - false - }); - let filterlistmodel = >k::FilterListModel::new(Some(model.clone()), Some(filter)); self.playbacklist.bind_model( - Some(filterlistmodel), - clone!(@weak window => @default-panic, move |item| { + Some(&manager.stream_output_model()), + clone!(@weak self as window => @default-panic, move |item| { PwOutputBox::new( item.downcast_ref::() .expect("RowData is of wrong type"), @@ -127,17 +113,9 @@ mod imp { }), ); - let filter = gtk::CustomFilter::new(|x| { - if let Some(o) = x.downcast_ref::() { - return o.nodetype() == NodeType::StreamInput; - } - false - }); - let filterlistmodel = >k::FilterListModel::new(Some(model.clone()), Some(filter)); - self.recordlist.bind_model( - Some(filterlistmodel), - clone!(@weak window => @default-panic, move |item| { + Some(&manager.stream_input_model()), + clone!(@weak self as window => @default-panic, move |item| { PwVolumeBox::new( item.downcast_ref::() .expect("RowData is of wrong type"), @@ -147,8 +125,8 @@ mod imp { ); self.inputlist.bind_model( - Some(sourcemodel), - clone!(@weak window => @default-panic, move |item| { + Some(&manager.source_model()), + clone!(@weak self as window => @default-panic, move |item| { PwSinkBox::new( item.downcast_ref::() .expect("RowData is of wrong type"), @@ -158,8 +136,8 @@ mod imp { ); self.outputlist.bind_model( - Some(sinkmodel), - clone!(@weak window => @default-panic, move |item| { + Some(&manager.sink_model()), + clone!(@weak self as window => @default-panic, move |item| { PwSinkBox::new( item.downcast_ref::() .expect("RowData is of wrong type"), @@ -169,8 +147,8 @@ mod imp { ); self.cardlist.bind_model( - Some(devicemodel), - clone!(@weak window => @default-panic, move |item| { + Some(&manager.device_model()), + clone!(@weak self as window => @default-panic, move |item| { let obj: &PwDeviceObject = item.downcast_ref().expect("PwDeviceObject"); PwDeviceBox::new(obj).upcast::() }), diff --git a/src/ui/withdefaultlistmodel.rs b/src/ui/withdefaultlistmodel.rs index 9a06d24..9ad9b0d 100644 --- a/src/ui/withdefaultlistmodel.rs +++ b/src/ui/withdefaultlistmodel.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use crate::backend::PwNodeModel; +use crate::backend::PwNodeFilterModel; use glib::{Properties, closure_local}; use gtk::{gio, glib, prelude::*, subclass::prelude::*}; use std::cell::RefCell; @@ -16,7 +16,7 @@ mod imp { pub(super) flatten_list_model: RefCell>, #[property(get, set = Self::set_model)] - pub(super) model: RefCell>, + pub(super) model: RefCell>, } impl Default for WithDefaultListModel { @@ -63,7 +63,7 @@ mod imp { } impl WithDefaultListModel { - pub fn set_model(&self, new_model: Option<&PwNodeModel>) { + pub fn set_model(&self, new_model: Option<&PwNodeFilterModel>) { let removed = self.n_items(); let string_list = self.string_list.borrow().clone(); @@ -96,7 +96,7 @@ glib::wrapper! { } impl WithDefaultListModel { - pub(crate) fn new(model: Option<&PwNodeModel>) -> Self { + pub(crate) fn new(model: Option<&PwNodeFilterModel>) -> Self { glib::Object::builder().property("model", model).build() }