mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-25 10:30:59 +00:00
player: generate a graph from a pipeline description
This commit is contained in:
parent
01df04be60
commit
88ec98bcef
6 changed files with 322 additions and 17 deletions
56
src/app.rs
56
src/app.rs
|
@ -258,6 +258,9 @@ impl GPSApp {
|
||||||
application.add_action(&gio::SimpleAction::new("open", None));
|
application.add_action(&gio::SimpleAction::new("open", None));
|
||||||
application.set_accels_for_action("app.open", &["<primary>o"]);
|
application.set_accels_for_action("app.open", &["<primary>o"]);
|
||||||
|
|
||||||
|
application.add_action(&gio::SimpleAction::new("open_pipeline", None));
|
||||||
|
application.set_accels_for_action("app.open_pipeline", &["<primary>p"]);
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("save_as", None));
|
application.add_action(&gio::SimpleAction::new("save_as", None));
|
||||||
application.set_accels_for_action("app.save", &["<primary>s"]);
|
application.set_accels_for_action("app.save", &["<primary>s"]);
|
||||||
|
|
||||||
|
@ -488,6 +491,22 @@ impl GPSApp {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let app_weak = self.downgrade();
|
||||||
|
self.connect_app_menu_action("open_pipeline", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPSUI::dialog::create_input_dialog(
|
||||||
|
"Enter pipeline description with gst-launch format",
|
||||||
|
"description",
|
||||||
|
&Settings::recent_pipeline_description(),
|
||||||
|
&app,
|
||||||
|
move |app, pipeline_desc| {
|
||||||
|
app.load_pipeline(&pipeline_desc)
|
||||||
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", pipeline_desc));
|
||||||
|
Settings::set_recent_pipeline_description(&pipeline_desc);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("save_as", move |_, _| {
|
self.connect_app_menu_action("save_as", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
|
@ -631,11 +650,11 @@ impl GPSApp {
|
||||||
app.connect_app_menu_action("graph.check",
|
app.connect_app_menu_action("graph.check",
|
||||||
move |_,_| {
|
move |_,_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let render_parse_launch = app.player.borrow().render_gst_launch(&app.graphview.borrow());
|
let pipeline_description = app.player.borrow().pipeline_description_from_graphview(&app.graphview.borrow());
|
||||||
if app.player.borrow().create_pipeline(&render_parse_launch).is_ok() {
|
if app.player.borrow().create_pipeline(&pipeline_description).is_ok() {
|
||||||
GPSUI::message::display_message_dialog(&render_parse_launch,gtk::MessageType::Info, |_| {});
|
GPSUI::message::display_message_dialog(&pipeline_description,gtk::MessageType::Info, |_| {});
|
||||||
} else {
|
} else {
|
||||||
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{render_parse_launch}", ));
|
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{pipeline_description}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -752,7 +771,7 @@ impl GPSApp {
|
||||||
move |_,_| {
|
move |_,_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
GPS_DEBUG!("node.request-pad-input {}", node_id);
|
GPS_DEBUG!("node.request-pad-input {}", node_id);
|
||||||
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string())
|
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -908,13 +927,13 @@ impl GPSApp {
|
||||||
properties
|
properties
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_port_with_caps(
|
pub fn create_port_with_caps(
|
||||||
&self,
|
&self,
|
||||||
node_id: u32,
|
node_id: u32,
|
||||||
direction: GM::PortDirection,
|
direction: GM::PortDirection,
|
||||||
presence: GM::PortPresence,
|
presence: GM::PortPresence,
|
||||||
caps: String,
|
caps: String,
|
||||||
) {
|
) -> u32 {
|
||||||
let node = self.node(node_id);
|
let node = self.node(node_id);
|
||||||
let ports = node.all_ports(direction);
|
let ports = node.all_ports(direction);
|
||||||
let port_name = match direction {
|
let port_name = match direction {
|
||||||
|
@ -925,11 +944,27 @@ impl GPSApp {
|
||||||
let graphview = self.graphview.borrow();
|
let graphview = self.graphview.borrow();
|
||||||
let port_name = format!("{}{}", port_name, ports.len());
|
let port_name = format!("{}{}", port_name, ports.len());
|
||||||
let port = graphview.create_port(&port_name, direction, presence);
|
let port = graphview.create_port(&port_name, direction, presence);
|
||||||
|
let id = port.id();
|
||||||
let properties: HashMap<String, String> = HashMap::from([("_caps".to_string(), caps)]);
|
let properties: HashMap<String, String> = HashMap::from([("_caps".to_string(), caps)]);
|
||||||
port.update_properties(&properties);
|
port.update_properties(&properties);
|
||||||
if let Some(mut node) = graphview.node(node_id) {
|
if let Some(mut node) = graphview.node(node_id) {
|
||||||
graphview.add_port_to_node(&mut node, port);
|
graphview.add_port_to_node(&mut node, port);
|
||||||
}
|
}
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_link(
|
||||||
|
&self,
|
||||||
|
node_from_id: u32,
|
||||||
|
node_to_id: u32,
|
||||||
|
port_from_id: u32,
|
||||||
|
port_to_id: u32,
|
||||||
|
active: bool,
|
||||||
|
) {
|
||||||
|
let graphview = self.graphview.borrow();
|
||||||
|
let link =
|
||||||
|
graphview.create_link(node_from_id, node_to_id, port_from_id, port_to_id, active);
|
||||||
|
graphview.add_link(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_graph(&self) {
|
fn clear_graph(&self) {
|
||||||
|
@ -954,4 +989,11 @@ impl GPSApp {
|
||||||
graph_view.load_from_xml(buffer)?;
|
graph_view.load_from_xml(buffer)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_pipeline(&self, pipeline_desc: &str) -> anyhow::Result<()> {
|
||||||
|
let player = self.player.borrow();
|
||||||
|
let graphview = self.graphview.borrow();
|
||||||
|
player.graphview_from_pipeline_description(&graphview, pipeline_desc);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::app::{AppState, GPSApp, GPSAppWeak};
|
use crate::app::{AppState, GPSApp, GPSAppWeak};
|
||||||
use crate::graphmanager::{GraphView, Node, NodeType, PortDirection, PropertyExt};
|
use crate::graphmanager as GM;
|
||||||
|
use crate::graphmanager::PropertyExt;
|
||||||
|
|
||||||
|
use crate::common;
|
||||||
use crate::gps::ElementInfo;
|
use crate::gps::ElementInfo;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
|
@ -165,14 +167,14 @@ impl Player {
|
||||||
|
|
||||||
pub fn start_pipeline(
|
pub fn start_pipeline(
|
||||||
&self,
|
&self,
|
||||||
graphview: &GraphView,
|
graphview: &GM::GraphView,
|
||||||
new_state: PipelineState,
|
new_state: PipelineState,
|
||||||
) -> anyhow::Result<PipelineState> {
|
) -> anyhow::Result<PipelineState> {
|
||||||
if self.state() == PipelineState::Stopped || self.state() == PipelineState::Error {
|
if self.state() == PipelineState::Stopped || self.state() == PipelineState::Error {
|
||||||
let pipeline = self
|
let pipeline = self
|
||||||
.create_pipeline(&self.render_gst_launch(graphview))
|
.create_pipeline(&self.pipeline_description_from_graphview(graphview))
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
GPS_ERROR!("Unable to start a pipeline: {}", err);
|
GPS_ERROR!("Unable to create a pipeline: {}", err);
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -328,8 +330,8 @@ impl Player {
|
||||||
#[allow(clippy::only_used_in_recursion)]
|
#[allow(clippy::only_used_in_recursion)]
|
||||||
fn process_gst_node(
|
fn process_gst_node(
|
||||||
&self,
|
&self,
|
||||||
graphview: &GraphView,
|
graphview: &GM::GraphView,
|
||||||
node: &Node,
|
node: &GM::Node,
|
||||||
elements: &mut HashMap<String, String>,
|
elements: &mut HashMap<String, String>,
|
||||||
mut description: String,
|
mut description: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
@ -344,7 +346,7 @@ impl Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Port properties
|
//Port properties
|
||||||
let ports = node.all_ports(PortDirection::All);
|
let ports = node.all_ports(GM::PortDirection::All);
|
||||||
for port in ports {
|
for port in ports {
|
||||||
for (name, value) in port.properties().iter() {
|
for (name, value) in port.properties().iter() {
|
||||||
if !port.hidden_property(name) {
|
if !port.hidden_property(name) {
|
||||||
|
@ -353,7 +355,7 @@ impl Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ports = node.all_ports(PortDirection::Output);
|
let ports = node.all_ports(GM::PortDirection::Output);
|
||||||
let n_ports = ports.len();
|
let n_ports = ports.len();
|
||||||
for port in ports {
|
for port in ports {
|
||||||
if let Some((_port_to, node_to)) = graphview.port_connected_to(port.id()) {
|
if let Some((_port_to, node_to)) = graphview.port_connected_to(port.id()) {
|
||||||
|
@ -375,8 +377,8 @@ impl Player {
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_gst_launch(&self, graphview: &GraphView) -> String {
|
pub fn pipeline_description_from_graphview(&self, graphview: &GM::GraphView) -> String {
|
||||||
let source_nodes = graphview.all_nodes(NodeType::Source);
|
let source_nodes = graphview.all_nodes(GM::NodeType::Source);
|
||||||
let mut elements: HashMap<String, String> = HashMap::new();
|
let mut elements: HashMap<String, String> = HashMap::new();
|
||||||
let mut description = String::from("");
|
let mut description = String::from("");
|
||||||
for source_node in source_nodes {
|
for source_node in source_nodes {
|
||||||
|
@ -385,6 +387,140 @@ impl Player {
|
||||||
}
|
}
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_links_for_element(&self, element: &gst::Element, graphview: &GM::GraphView) {
|
||||||
|
let mut iter = element.iterate_pads();
|
||||||
|
let node = graphview
|
||||||
|
.node_by_unique_name(&element.name())
|
||||||
|
.expect("node should exists");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match iter.next() {
|
||||||
|
Ok(Some(pad)) => {
|
||||||
|
GPS_INFO!("Found pad: {}", pad.name());
|
||||||
|
|
||||||
|
if pad.direction() == gst::PadDirection::Src {
|
||||||
|
let port = node
|
||||||
|
.port_by_name(&pad.name())
|
||||||
|
.expect("The port should exist here");
|
||||||
|
if let Some(peer_pad) = pad.peer() {
|
||||||
|
if let Some(peer_element) = peer_pad.parent_element() {
|
||||||
|
let peer_node = graphview
|
||||||
|
.node_by_unique_name(&peer_element.name())
|
||||||
|
.expect("The node should exists here");
|
||||||
|
let peer_port = peer_node
|
||||||
|
.port_by_name(&peer_pad.name())
|
||||||
|
.expect("The port should exists here");
|
||||||
|
self.app.borrow().as_ref().unwrap().create_link(
|
||||||
|
node.id(),
|
||||||
|
peer_node.id(),
|
||||||
|
port.id(),
|
||||||
|
peer_port.id(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(gst::IteratorError::Resync) => iter.resync(),
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_pads_for_element(&self, element: &gst::Element, node: &GM::Node) {
|
||||||
|
let mut iter = element.iterate_pads();
|
||||||
|
loop {
|
||||||
|
match iter.next() {
|
||||||
|
Ok(Some(pad)) => {
|
||||||
|
let pad_name = pad.name().to_string();
|
||||||
|
GPS_INFO!("Found pad: {}", pad_name);
|
||||||
|
let mut port_direction = GM::PortDirection::Input;
|
||||||
|
if pad.direction() == gst::PadDirection::Src {
|
||||||
|
port_direction = GM::PortDirection::Output;
|
||||||
|
}
|
||||||
|
let port_id = self.app.borrow().as_ref().unwrap().create_port_with_caps(
|
||||||
|
node.id(),
|
||||||
|
port_direction,
|
||||||
|
GM::PortPresence::Always,
|
||||||
|
pad.current_caps()
|
||||||
|
.unwrap_or_else(|| pad.query_caps(None))
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
if let Some(port) = node.port(port_id) {
|
||||||
|
port.set_name(&pad_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(gst::IteratorError::Resync) => iter.resync(),
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_properties_for_element(&self, element: &gst::Element, node: &GM::Node) {
|
||||||
|
let properties = ElementInfo::element_properties(element)
|
||||||
|
.unwrap_or_else(|_| panic!("Couldn't get properties for {}", node.name()));
|
||||||
|
for (property_name, property_value) in properties {
|
||||||
|
if property_name == "name"
|
||||||
|
|| property_name == "parent"
|
||||||
|
|| (property_value.flags() & glib::ParamFlags::READABLE)
|
||||||
|
!= glib::ParamFlags::READABLE
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(value_str) = ElementInfo::element_property(element, &property_name) {
|
||||||
|
let default_value_str =
|
||||||
|
common::value_as_str(property_value.default_value()).unwrap_or_default();
|
||||||
|
GPS_DEBUG!(
|
||||||
|
"property name {} value_str '{}' default '{}'",
|
||||||
|
property_name,
|
||||||
|
value_str,
|
||||||
|
default_value_str
|
||||||
|
);
|
||||||
|
if !value_str.is_empty() && value_str != default_value_str {
|
||||||
|
node.add_property(&property_name, &value_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphview_from_pipeline_description(
|
||||||
|
&self,
|
||||||
|
graphview: &GM::GraphView,
|
||||||
|
pipeline_desc: &str,
|
||||||
|
) {
|
||||||
|
graphview.clear();
|
||||||
|
|
||||||
|
if let Ok(pipeline) = self.create_pipeline(pipeline_desc) {
|
||||||
|
let mut iter = pipeline.iterate_elements();
|
||||||
|
let mut elements: Vec<gst::Element> = Vec::new();
|
||||||
|
let elements = loop {
|
||||||
|
match iter.next() {
|
||||||
|
Ok(Some(element)) => {
|
||||||
|
GPS_INFO!("Found element: {}", element.name());
|
||||||
|
let element_factory_name = element.factory().unwrap().name().to_string();
|
||||||
|
let node = graphview.create_node(
|
||||||
|
&element_factory_name,
|
||||||
|
ElementInfo::element_type(&element_factory_name),
|
||||||
|
);
|
||||||
|
node.set_unique_name(&element.name());
|
||||||
|
graphview.add_node(node.clone());
|
||||||
|
self.create_pads_for_element(&element, &node);
|
||||||
|
self.create_properties_for_element(&element, &node);
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
Err(gst::IteratorError::Resync) => iter.resync(),
|
||||||
|
_ => break elements,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for element in elements {
|
||||||
|
self.create_links_for_element(&element, graphview);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GPS_ERROR!("Unable to create a pipeline: {}", pipeline_desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for PlayerInner {
|
impl Drop for PlayerInner {
|
||||||
|
|
58
src/gps/test.rs
Normal file
58
src/gps/test.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::gps::player::Player;
|
||||||
|
use crate::graphmanager as GM;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn test_synced<F, R>(function: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R + Send + std::panic::UnwindSafe + 'static,
|
||||||
|
R: Send + 'static,
|
||||||
|
{
|
||||||
|
/// No-op.
|
||||||
|
macro_rules! skip_assert_initialized {
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
skip_assert_initialized!();
|
||||||
|
|
||||||
|
use futures_channel::oneshot;
|
||||||
|
use std::panic;
|
||||||
|
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
TEST_THREAD_WORKER
|
||||||
|
.push(move || {
|
||||||
|
tx.send(panic::catch_unwind(function))
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to return result from thread pool"));
|
||||||
|
})
|
||||||
|
.expect("Failed to schedule a test call");
|
||||||
|
futures_executor::block_on(rx)
|
||||||
|
.expect("Failed to receive result from thread pool")
|
||||||
|
.unwrap_or_else(|e| std::panic::resume_unwind(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
static TEST_THREAD_WORKER: once_cell::sync::Lazy<gtk::glib::ThreadPool> =
|
||||||
|
once_cell::sync::Lazy::new(|| {
|
||||||
|
let pool = gtk::glib::ThreadPool::exclusive(1).unwrap();
|
||||||
|
pool.push(move || {
|
||||||
|
gtk::init().expect("Tests failed to initialize gtk");
|
||||||
|
})
|
||||||
|
.expect("Failed to schedule a test call");
|
||||||
|
pool
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod player_test {
|
||||||
|
use super::*;
|
||||||
|
fn check_pipeline(pipeline_desc: &str) {
|
||||||
|
let player = Player::new().expect("Not able to create the player");
|
||||||
|
let graphview = GM::GraphView::new();
|
||||||
|
player.graphview_from_pipeline_description(&graphview, pipeline_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipeline_creation() {
|
||||||
|
test_synced(|| {
|
||||||
|
println!("coucou");
|
||||||
|
//check_pipeline("videotestsrc ! autovideosink");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,10 +17,12 @@ use crate::config;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub app_maximized: bool,
|
pub app_maximized: bool,
|
||||||
pub app_width: i32,
|
pub app_width: i32,
|
||||||
pub app_height: i32,
|
pub app_height: i32,
|
||||||
|
pub recent_pipeline: String,
|
||||||
|
|
||||||
// values must be emitted before tables
|
// values must be emitted before tables
|
||||||
pub favorites: Vec<String>,
|
pub favorites: Vec<String>,
|
||||||
|
@ -67,6 +69,17 @@ impl Settings {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_recent_pipeline_description(pipeline: &str) {
|
||||||
|
let mut settings = Settings::load_settings();
|
||||||
|
settings.recent_pipeline = pipeline.to_string();
|
||||||
|
Settings::save_settings(&settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recent_pipeline_description() -> String {
|
||||||
|
let settings = Settings::load_settings();
|
||||||
|
settings.recent_pipeline
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_favorite(favorite: &str) {
|
pub fn add_favorite(favorite: &str) {
|
||||||
let mut settings = Settings::load_settings();
|
let mut settings = Settings::load_settings();
|
||||||
settings.favorites.sort();
|
settings.favorites.sort();
|
||||||
|
|
|
@ -43,3 +43,54 @@ pub fn create_dialog<F: Fn(GPSApp, gtk::Dialog) + 'static>(
|
||||||
|
|
||||||
dialog
|
dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_input_dialog<F: Fn(GPSApp, String) + 'static>(
|
||||||
|
dialog_name: &str,
|
||||||
|
input_name: &str,
|
||||||
|
default_value: &str,
|
||||||
|
app: &GPSApp,
|
||||||
|
f: F,
|
||||||
|
) {
|
||||||
|
let dialog = gtk::Dialog::with_buttons(
|
||||||
|
Some(dialog_name),
|
||||||
|
Some(&app.window),
|
||||||
|
gtk::DialogFlags::MODAL,
|
||||||
|
&[("Ok", gtk::ResponseType::Apply)],
|
||||||
|
);
|
||||||
|
dialog.set_default_size(600, 100);
|
||||||
|
dialog.set_modal(true);
|
||||||
|
|
||||||
|
let label = gtk::Label::builder()
|
||||||
|
.label(input_name)
|
||||||
|
.hexpand(true)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.margin_start(4)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let entry = gtk::Entry::builder()
|
||||||
|
.width_request(400)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
entry.set_text(default_value);
|
||||||
|
|
||||||
|
let content_area = dialog.content_area();
|
||||||
|
content_area.set_orientation(gtk::Orientation::Horizontal);
|
||||||
|
content_area.set_vexpand(true);
|
||||||
|
content_area.set_margin_start(10);
|
||||||
|
content_area.set_margin_end(10);
|
||||||
|
content_area.set_margin_top(10);
|
||||||
|
content_area.set_margin_bottom(10);
|
||||||
|
content_area.append(&label);
|
||||||
|
content_area.append(&entry);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
dialog.connect_response(glib::clone!(@weak entry => move |dialog, response_type| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
if response_type == gtk::ResponseType::Apply {
|
||||||
|
f(app, entry.text().to_string());
|
||||||
|
}
|
||||||
|
dialog.close()
|
||||||
|
}));
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
<attribute name="action">app.open</attribute>
|
<attribute name="action">app.open</attribute>
|
||||||
<attribute name="accel"><primary>n</attribute>
|
<attribute name="accel"><primary>n</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="Primary menu entry that opens a pipeline">_Open pipeline</attribute>
|
||||||
|
<attribute name="action">app.open_pipeline</attribute>
|
||||||
|
<attribute name="accel"><primary>p</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
|
||||||
<attribute name="action">app.save_as</attribute>
|
<attribute name="action">app.save_as</attribute>
|
||||||
|
|
Loading…
Reference in a new issue