mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-22 00:50:59 +00:00
app: manage graphtab as a tab of notebook
graph tab in the graphbook containing a player and a graphview
This commit is contained in:
parent
bcc39acf5e
commit
4a5964c340
9 changed files with 613 additions and 390 deletions
|
@ -87,6 +87,7 @@
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
### app
|
### app
|
||||||
- [x] handle the caps setter element
|
- [x] handle the caps setter element
|
||||||
|
- [x] Add multiple graphviews with tabs.
|
||||||
|
|
||||||
### CI/Infra
|
### CI/Infra
|
||||||
- [x] Create a macos installer
|
- [x] Create a macos installer
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -12,8 +12,7 @@
|
||||||
### app
|
### app
|
||||||
|
|
||||||
- [ ] Control the connection between element
|
- [ ] Control the connection between element
|
||||||
- [ ] unable to connect element with incompatible caps.
|
- [ ] unable to connect element with incompatible caps.
|
||||||
- [ ] Add multiple graphviews with tabs.
|
|
||||||
- [ ] Implement graph dot render/load
|
- [ ] Implement graph dot render/load
|
||||||
|
|
||||||
|
|
||||||
|
|
440
src/app.rs
440
src/app.rs
|
@ -7,17 +7,16 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use glib::SignalHandlerId;
|
use glib::SignalHandlerId;
|
||||||
use glib::Value;
|
|
||||||
use gtk::gdk;
|
use gtk::gdk;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{gio, gio::SimpleAction, glib, graphene};
|
use gtk::{gio, gio::SimpleAction, glib, graphene};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
Application, ApplicationWindow, Builder, Button, FileChooserAction, FileChooserDialog, Paned,
|
Application, ApplicationWindow, Builder, Button, FileChooserAction, FileChooserDialog, Paned,
|
||||||
PopoverMenu, ResponseType, Statusbar, Viewport, Widget,
|
PopoverMenu, ResponseType, Statusbar, Widget,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
use std::cell::{Ref, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -25,12 +24,12 @@ use std::ops;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
use crate::gps as GPS;
|
use crate::gps as GPS;
|
||||||
|
use crate::graphbook;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
use crate::session::GPSSession;
|
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::ui as GPSUI;
|
use crate::ui as GPSUI;
|
||||||
|
|
||||||
use crate::{GPS_DEBUG, GPS_ERROR, GPS_INFO, GPS_TRACE, GPS_WARN};
|
use crate::{GPS_DEBUG, GPS_ERROR, GPS_TRACE, GPS_WARN};
|
||||||
|
|
||||||
use crate::graphmanager as GM;
|
use crate::graphmanager as GM;
|
||||||
use crate::graphmanager::PropertyExt;
|
use crate::graphmanager::PropertyExt;
|
||||||
|
@ -38,7 +37,8 @@ use std::fmt;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GPSAppInner {
|
pub struct GPSAppInner {
|
||||||
pub window: gtk::ApplicationWindow,
|
pub window: gtk::ApplicationWindow,
|
||||||
pub session: RefCell<GPSSession>,
|
pub current_graphtab: Cell<u32>,
|
||||||
|
pub graphbook: RefCell<HashMap<u32, graphbook::GraphTab>>,
|
||||||
pub builder: Builder,
|
pub builder: Builder,
|
||||||
pub plugin_list_initialized: OnceCell<bool>,
|
pub plugin_list_initialized: OnceCell<bool>,
|
||||||
pub signal_handlers: RefCell<HashMap<String, SignalHandlerId>>,
|
pub signal_handlers: RefCell<HashMap<String, SignalHandlerId>>,
|
||||||
|
@ -97,15 +97,12 @@ impl GPSApp {
|
||||||
|
|
||||||
let app = GPSApp(Rc::new(GPSAppInner {
|
let app = GPSApp(Rc::new(GPSAppInner {
|
||||||
window,
|
window,
|
||||||
session: RefCell::new(GPSSession::new()),
|
current_graphtab: Cell::new(0),
|
||||||
|
graphbook: RefCell::new(HashMap::new()),
|
||||||
builder,
|
builder,
|
||||||
plugin_list_initialized: OnceCell::new(),
|
plugin_list_initialized: OnceCell::new(),
|
||||||
signal_handlers: RefCell::new(HashMap::new()),
|
signal_handlers: RefCell::new(HashMap::new()),
|
||||||
}));
|
}));
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.session.borrow().set_player(app_weak);
|
|
||||||
app.session.borrow().set_graphview_id(0);
|
|
||||||
|
|
||||||
let settings = Settings::load_settings();
|
let settings = Settings::load_settings();
|
||||||
|
|
||||||
if settings.app_maximized {
|
if settings.app_maximized {
|
||||||
|
@ -123,10 +120,6 @@ impl GPSApp {
|
||||||
Ok(app)
|
Ok(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session(&self) -> Ref<GPSSession> {
|
|
||||||
self.session.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_paned_position(
|
fn set_paned_position(
|
||||||
&self,
|
&self,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
|
@ -182,7 +175,11 @@ impl GPSApp {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let value = slider.value() as u64;
|
let value = slider.value() as u64;
|
||||||
GPS_TRACE!("Seeking to {} s", value);
|
GPS_TRACE!("Seeking to {} s", value);
|
||||||
if app.session().player().set_position(value).is_err() {
|
if graphbook::current_graphtab(&app)
|
||||||
|
.player()
|
||||||
|
.set_position(value)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
GPS_ERROR!("Seeking to {} failed", value);
|
GPS_ERROR!("Seeking to {} failed", value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -199,15 +196,17 @@ impl GPSApp {
|
||||||
.builder
|
.builder
|
||||||
.object("scale-position")
|
.object("scale-position")
|
||||||
.expect("Couldn't get status_bar");
|
.expect("Couldn't get status_bar");
|
||||||
let position = app.session().player().position();
|
let position = graphbook::current_graphtab(&app).player().position();
|
||||||
let duration = app.session().player().duration();
|
let duration = graphbook::current_graphtab(&app).player().duration();
|
||||||
slider.set_range(0.0, duration as f64 / 1000_f64);
|
slider.set_range(0.0, duration as f64 / 1000_f64);
|
||||||
slider.block_signal(&slider_update_signal_id);
|
slider.block_signal(&slider_update_signal_id);
|
||||||
slider.set_value(position as f64 / 1000_f64);
|
slider.set_value(position as f64 / 1000_f64);
|
||||||
slider.unblock_signal(&slider_update_signal_id);
|
slider.unblock_signal(&slider_update_signal_id);
|
||||||
|
|
||||||
// Query the current playing position from the underlying player.
|
// Query the current playing position from the underlying player.
|
||||||
let position_desc = app.session().player().position_description();
|
let position_desc = graphbook::current_graphtab(&app)
|
||||||
|
.player()
|
||||||
|
.position_description();
|
||||||
// Display the playing position in the gui.
|
// Display the playing position in the gui.
|
||||||
label.set_text(&position_desc);
|
label.set_text(&position_desc);
|
||||||
// Tell the callback to continue calling this closure.
|
// Tell the callback to continue calling this closure.
|
||||||
|
@ -262,7 +261,9 @@ impl GPSApp {
|
||||||
application.set_accels_for_action("app.open_pipeline", &["<primary>p"]);
|
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.add_action(&gio::SimpleAction::new("save", None));
|
||||||
application.set_accels_for_action("app.save", &["<primary>s"]);
|
application.set_accels_for_action("app.save", &["<primary>s"]);
|
||||||
|
application.add_action(&gio::SimpleAction::new("save_as", None));
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("delete", None));
|
application.add_action(&gio::SimpleAction::new("delete", None));
|
||||||
application.set_accels_for_action("app.delete", &["<primary>d", "Delete"]);
|
application.set_accels_for_action("app.delete", &["<primary>d", "Delete"]);
|
||||||
|
@ -278,6 +279,7 @@ impl GPSApp {
|
||||||
application.add_action(&gio::SimpleAction::new("logger.clear", None));
|
application.add_action(&gio::SimpleAction::new("logger.clear", None));
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("graph.check", None));
|
application.add_action(&gio::SimpleAction::new("graph.check", None));
|
||||||
|
application.add_action(&gio::SimpleAction::new("graph.clear", None));
|
||||||
application.add_action(&gio::SimpleAction::new("graph.pipeline_details", None));
|
application.add_action(&gio::SimpleAction::new("graph.pipeline_details", None));
|
||||||
|
|
||||||
application.add_action(&gio::SimpleAction::new("port.delete", None));
|
application.add_action(&gio::SimpleAction::new("port.delete", None));
|
||||||
|
@ -331,7 +333,7 @@ impl GPSApp {
|
||||||
.expect("Unable to dynamic cast to SimpleAction")
|
.expect("Unable to dynamic cast to SimpleAction")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disconnect_app_menu_action(&self, action_name: &str) {
|
pub fn disconnect_app_menu_action(&self, action_name: &str) {
|
||||||
let action = self.app_menu_action(action_name);
|
let action = self.app_menu_action(action_name);
|
||||||
|
|
||||||
if let Some(signal_handler_id) = self.signal_handlers.borrow_mut().remove(action_name) {
|
if let Some(signal_handler_id) = self.signal_handlers.borrow_mut().remove(action_name) {
|
||||||
|
@ -420,7 +422,12 @@ impl GPSApp {
|
||||||
.builder
|
.builder
|
||||||
.object("notebook-preview")
|
.object("notebook-preview")
|
||||||
.expect("Couldn't get box_preview");
|
.expect("Couldn't get box_preview");
|
||||||
if n_sink == 0 {
|
let mut n_video_sink = n_sink;
|
||||||
|
for tab in self.graphbook.borrow().values() {
|
||||||
|
n_video_sink += tab.player().n_video_sink();
|
||||||
|
}
|
||||||
|
|
||||||
|
if n_video_sink == 0 {
|
||||||
loop {
|
loop {
|
||||||
let i = notebook_preview.n_pages();
|
let i = notebook_preview.n_pages();
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
@ -435,17 +442,14 @@ impl GPSApp {
|
||||||
.valign(gtk::Align::Center)
|
.valign(gtk::Align::Center)
|
||||||
.build();
|
.build();
|
||||||
box_preview.append(&picture);
|
box_preview.append(&picture);
|
||||||
let label = gtk::Label::new(Some(&format!("Preview{n_sink}")));
|
let label = gtk::Label::new(Some(&format!("Preview{n_video_sink}")));
|
||||||
notebook_preview.insert_page(&box_preview, Some(&label), None);
|
notebook_preview.insert_page(&box_preview, Some(&label), None);
|
||||||
|
notebook_preview.set_current_page(Some(n_video_sink as u32));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_ui(&self, application: &Application) {
|
pub fn build_ui(&self, application: &Application) {
|
||||||
let drawing_area_window: Viewport = self
|
graphbook::setup_graphbook(self);
|
||||||
.builder
|
graphbook::create_graphtab(self, 0, None);
|
||||||
.object("drawing_area")
|
|
||||||
.expect("Couldn't get drawing_area");
|
|
||||||
|
|
||||||
drawing_area_window.set_child(Some(&*self.session.borrow().graphview()));
|
|
||||||
|
|
||||||
// Setup the logger to get messages into the TreeView
|
// Setup the logger to get messages into the TreeView
|
||||||
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::Priority::DEFAULT);
|
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::Priority::DEFAULT);
|
||||||
|
@ -478,15 +482,20 @@ impl GPSApp {
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("new-window", move |_, _| {
|
self.connect_app_menu_action("new-window", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
app.clear_graph();
|
let id = graphbook::graphbook_get_new_graphtab_id(&app);
|
||||||
GPS_ERROR!("clear graph");
|
graphbook::create_graphtab(&app, id, None);
|
||||||
|
let graphbook: gtk::Notebook = app
|
||||||
|
.builder
|
||||||
|
.object("graphbook")
|
||||||
|
.expect("Couldn't get graphbook");
|
||||||
|
graphbook.set_current_page(Some(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("open", move |_, _| {
|
self.connect_app_menu_action("open", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
GPSApp::get_file_from_dialog(&app, false, move |app, filename| {
|
GPSApp::get_file_from_dialog(&app, false, move |app, filename| {
|
||||||
app.load_graph(&filename)
|
app.load_graph(&filename, false)
|
||||||
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", filename));
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", filename));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -506,6 +515,24 @@ impl GPSApp {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
let app_weak = self.downgrade();
|
||||||
|
self.connect_app_menu_action("save", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
let gt = graphbook::current_graphtab(&app);
|
||||||
|
if gt.undefined() {
|
||||||
|
GPSApp::get_file_from_dialog(&app, true, move |app, filename| {
|
||||||
|
GPS_DEBUG!("Save file {}", filename);
|
||||||
|
app.save_graph(&filename)
|
||||||
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
||||||
|
graphbook::current_graphtab_set_filename(&app, filename.as_str());
|
||||||
|
});
|
||||||
|
} else if gt.modified() {
|
||||||
|
let filename = gt.filename();
|
||||||
|
app.save_graph(&filename)
|
||||||
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
||||||
|
graphbook::current_graphtab_set_filename(&app, filename.as_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
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 |_, _| {
|
||||||
|
@ -514,6 +541,7 @@ impl GPSApp {
|
||||||
GPS_DEBUG!("Save file {}", filename);
|
GPS_DEBUG!("Save file {}", filename);
|
||||||
app.save_graph(&filename)
|
app.save_graph(&filename)
|
||||||
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
.unwrap_or_else(|_| GPS_ERROR!("Unable to save file to {}", filename));
|
||||||
|
graphbook::current_graphtab_set_filename(&app, filename.as_str());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -526,7 +554,9 @@ impl GPSApp {
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_app_menu_action("delete", move |_, _| {
|
self.connect_app_menu_action("delete", move |_, _| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
app.session().graphview().delete_selected();
|
graphbook::current_graphtab(&app)
|
||||||
|
.graphview()
|
||||||
|
.delete_selected();
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
|
@ -538,27 +568,26 @@ impl GPSApp {
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_button_action("button-play", move |_| {
|
self.connect_button_action("button-play", move |_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let _ = app
|
let _ = graphbook::current_graphtab(&app).player().start_pipeline(
|
||||||
.session()
|
&graphbook::current_graphtab(&app).graphview(),
|
||||||
.player()
|
GPS::PipelineState::Playing,
|
||||||
.start_pipeline(&app.session().graphview(), GPS::PipelineState::Playing);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_button_action("button-pause", move |_| {
|
self.connect_button_action("button-pause", move |_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
let _ = app
|
let _ = graphbook::current_graphtab(&app).player().start_pipeline(
|
||||||
.session()
|
&graphbook::current_graphtab(&app).graphview(),
|
||||||
.player()
|
GPS::PipelineState::Paused,
|
||||||
.start_pipeline(&app.session().graphview(), GPS::PipelineState::Paused);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_weak = self.downgrade();
|
let app_weak = self.downgrade();
|
||||||
self.connect_button_action("button-stop", move |_| {
|
self.connect_button_action("button-stop", move |_| {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
|
|
||||||
let _ = app
|
let _ = graphbook::current_graphtab(&app)
|
||||||
.session()
|
|
||||||
.player()
|
.player()
|
||||||
.set_state(GPS::PipelineState::Stopped);
|
.set_state(GPS::PipelineState::Stopped);
|
||||||
});
|
});
|
||||||
|
@ -568,277 +597,6 @@ impl GPSApp {
|
||||||
let app = upgrade_weak!(app_weak);
|
let app = upgrade_weak!(app_weak);
|
||||||
app.clear_graph();
|
app.clear_graph();
|
||||||
});
|
});
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview().connect_local(
|
|
||||||
"node-added",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
|
||||||
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
|
||||||
GPS_INFO!("Node added node id={} in graph id={}", node_id, graph_id);
|
|
||||||
if let Some(node) = app.session().graphview().node(node_id) {
|
|
||||||
let description = GPS::ElementInfo::element_description(&node.name()).ok();
|
|
||||||
node.set_tooltip_markup(description.as_deref());
|
|
||||||
for port in node.all_ports(GM::PortDirection::All) {
|
|
||||||
let caps = PropertyExt::property(&port,"_caps");
|
|
||||||
GPS_TRACE!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
|
||||||
port.set_tooltip_markup(caps.as_deref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview().connect_local(
|
|
||||||
"port-added",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
|
||||||
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
|
||||||
let port_id = values[3].get::<u32>().expect("port id in args[3]");
|
|
||||||
GPS_INFO!("Port added port id={} to node id={} in graph id={}", port_id, node_id, graph_id);
|
|
||||||
if let Some(node) = app.session().graphview().node(node_id) {
|
|
||||||
if let Some(port) = node.port(port_id) {
|
|
||||||
let caps = PropertyExt::property(&port, "_caps");
|
|
||||||
GPS_TRACE!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
|
||||||
port.set_tooltip_markup(caps.as_deref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview().connect_local(
|
|
||||||
"graph-updated",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let id = values[1].get::<u32>().expect("id in args[1]");
|
|
||||||
GPS_TRACE!("Graph updated id={}", id);
|
|
||||||
let _ = app
|
|
||||||
.save_graph(
|
|
||||||
Settings::default_graph_file_path()
|
|
||||||
.to_str()
|
|
||||||
.expect("Unable to convert to string"),
|
|
||||||
)
|
|
||||||
.map_err(|e| GPS_WARN!("Unable to save file {}", e));
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
// When user clicks on port with right button
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview()
|
|
||||||
.connect_local(
|
|
||||||
"graph-right-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let point = values[1].get::<graphene::Point>().expect("point in args[2]");
|
|
||||||
|
|
||||||
let pop_menu = app.app_pop_menu_at_position(&*app.session().graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
|
||||||
let menu: gio::MenuModel = app
|
|
||||||
.builder
|
|
||||||
.object("graph_menu")
|
|
||||||
.expect("Couldn't graph_menu");
|
|
||||||
pop_menu.set_menu_model(Some(&menu));
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("graph.check",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
let render_parse_launch = app.session().player().pipeline_description_from_graphview(&app.session().graphview());
|
|
||||||
if app.session().player().create_pipeline(&render_parse_launch).is_ok() {
|
|
||||||
GPSUI::message::display_message_dialog(&render_parse_launch,gtk::MessageType::Info, |_| {});
|
|
||||||
} else {
|
|
||||||
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{render_parse_launch}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("graph.pipeline_details",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPSUI::properties::display_pipeline_details(&app);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pop_menu.show();
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// When user clicks on port with right button
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview().connect_local(
|
|
||||||
"port-right-clicked",
|
|
||||||
false,
|
|
||||||
move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
|
|
||||||
let port_id = values[1].get::<u32>().expect("port id args[1]");
|
|
||||||
let node_id = values[2].get::<u32>().expect("node id args[2]");
|
|
||||||
let point = values[3]
|
|
||||||
.get::<graphene::Point>()
|
|
||||||
.expect("point in args[3]");
|
|
||||||
|
|
||||||
let pop_menu = app.app_pop_menu_at_position(
|
|
||||||
&*app.session().graphview(),
|
|
||||||
point.to_vec2().x() as f64,
|
|
||||||
point.to_vec2().y() as f64,
|
|
||||||
);
|
|
||||||
let menu: gio::MenuModel = app
|
|
||||||
.builder
|
|
||||||
.object("port_menu")
|
|
||||||
.expect("Couldn't get menu model for port");
|
|
||||||
pop_menu.set_menu_model(Some(&menu));
|
|
||||||
|
|
||||||
if app.session().graphview().can_remove_port(node_id, port_id) {
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("port.delete", move |_, _| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_TRACE!("port.delete-link port {} node {}", port_id, node_id);
|
|
||||||
app.session().graphview().remove_port(node_id, port_id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
app.disconnect_app_menu_action("port.delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("port.properties", move |_, _| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_TRACE!("port.properties port {} node {}", port_id, node_id);
|
|
||||||
let node = app.node(node_id);
|
|
||||||
let port = app.port(node_id, port_id);
|
|
||||||
GPSUI::properties::display_pad_properties(
|
|
||||||
&app,
|
|
||||||
&node.name(),
|
|
||||||
&port.name(),
|
|
||||||
node_id,
|
|
||||||
port_id,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
pop_menu.show();
|
|
||||||
None
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// When user clicks on node with right button
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview()
|
|
||||||
.connect_local(
|
|
||||||
"node-right-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
|
|
||||||
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
|
||||||
let point = values[2].get::<graphene::Point>().expect("point in args[2]");
|
|
||||||
|
|
||||||
let pop_menu = app.app_pop_menu_at_position(&*app.session().graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
|
||||||
let menu: gio::MenuModel = app
|
|
||||||
.builder
|
|
||||||
.object("node_menu")
|
|
||||||
.expect("Couldn't get menu model for node");
|
|
||||||
pop_menu.set_menu_model(Some(&menu));
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.add-to-favorite",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.add-to-favorite {}", node_id);
|
|
||||||
if let Some(node) = app.session().graphview().node(node_id) {
|
|
||||||
GPSUI::elements::add_to_favorite_list(&app, node.name());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.delete",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.delete {}", node_id);
|
|
||||||
app.session().graphview().remove_node(node_id);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let node = app.node(node_id);
|
|
||||||
if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) {
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.request-pad-input",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
app.disconnect_app_menu_action("node.request-pad-input");
|
|
||||||
}
|
|
||||||
let node = app.node(node_id);
|
|
||||||
if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) {
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.request-pad-output",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.request-pad-output {}", node_id);
|
|
||||||
app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
app.disconnect_app_menu_action("node.request-pad-output");
|
|
||||||
}
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
app.connect_app_menu_action("node.properties",
|
|
||||||
move |_,_| {
|
|
||||||
let app = upgrade_weak!(app_weak);
|
|
||||||
GPS_DEBUG!("node.properties {}", node_id);
|
|
||||||
let node = app.session().graphview().node(node_id).unwrap();
|
|
||||||
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
pop_menu.show();
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview.borrow().connect_local(
|
|
||||||
"node-double-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
|
||||||
GPS_TRACE!("Node double clicked id={}", node_id);
|
|
||||||
let node = app.session().graphview.borrow().node(node_id).unwrap();
|
|
||||||
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let app_weak = self.downgrade();
|
|
||||||
self.session().graphview.borrow().connect_local(
|
|
||||||
"link-double-clicked",
|
|
||||||
false,
|
|
||||||
glib::clone!(@weak application => @default-return None, move |values: &[Value]| {
|
|
||||||
let app = upgrade_weak!(app_weak, None);
|
|
||||||
let link_id = values[1].get::<u32>().expect("link id args[1]");
|
|
||||||
GPS_TRACE!("link double clicked id={}", link_id);
|
|
||||||
let link = app.session().graphview.borrow().link(link_id).unwrap();
|
|
||||||
GPSUI::dialog::create_input_dialog(
|
|
||||||
"Enter caps filter description",
|
|
||||||
"description",
|
|
||||||
&link.name(),
|
|
||||||
&app,
|
|
||||||
move |app, link_desc| {
|
|
||||||
GPS_ERROR!("link double clicked id={}", link.id());
|
|
||||||
app.session().graphview.borrow().set_link_name(link.id(), link_desc.as_str());
|
|
||||||
GPS_ERROR!("link double clicked name={}", link.name());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup the favorite list
|
// Setup the favorite list
|
||||||
GPSUI::elements::setup_favorite_list(self);
|
GPSUI::elements::setup_favorite_list(self);
|
||||||
|
@ -850,6 +608,7 @@ impl GPSApp {
|
||||||
Settings::default_graph_file_path()
|
Settings::default_graph_file_path()
|
||||||
.to_str()
|
.to_str()
|
||||||
.expect("Unable to convert to string"),
|
.expect("Unable to convert to string"),
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
.map_err(|_e| {
|
.map_err(|_e| {
|
||||||
GPS_WARN!("Unable to load default graph");
|
GPS_WARN!("Unable to load default graph");
|
||||||
|
@ -866,8 +625,7 @@ impl GPSApp {
|
||||||
|
|
||||||
pub fn add_new_element(&self, element_name: &str) {
|
pub fn add_new_element(&self, element_name: &str) {
|
||||||
let (inputs, outputs) = GPS::PadInfo::pads(element_name, false);
|
let (inputs, outputs) = GPS::PadInfo::pads(element_name, false);
|
||||||
let node = self
|
let node = graphbook::current_graphtab(self)
|
||||||
.session()
|
|
||||||
.graphview()
|
.graphview()
|
||||||
.create_node(element_name, GPS::ElementInfo::element_type(element_name));
|
.create_node(element_name, GPS::ElementInfo::element_type(element_name));
|
||||||
let node_id = node.id();
|
let node_id = node.id();
|
||||||
|
@ -876,12 +634,12 @@ impl GPSApp {
|
||||||
GPS_DEBUG!("Open file {}", filename);
|
GPS_DEBUG!("Open file {}", filename);
|
||||||
let mut properties: HashMap<String, String> = HashMap::new();
|
let mut properties: HashMap<String, String> = HashMap::new();
|
||||||
properties.insert(String::from("location"), filename);
|
properties.insert(String::from("location"), filename);
|
||||||
if let Some(node) = app.session().graphview().node(node_id) {
|
if let Some(node) = graphbook::current_graphtab(&app).graphview().node(node_id) {
|
||||||
node.update_properties(&properties);
|
node.update_properties(&properties);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.session().graphview().add_node(node);
|
graphbook::current_graphtab(self).graphview().add_node(node);
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
self.create_port_with_caps(
|
self.create_port_with_caps(
|
||||||
node_id,
|
node_id,
|
||||||
|
@ -900,16 +658,15 @@ impl GPSApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node(&self, node_id: u32) -> GM::Node {
|
pub fn node(&self, node_id: u32) -> GM::Node {
|
||||||
let node = self
|
let node = graphbook::current_graphtab(self)
|
||||||
.session()
|
|
||||||
.graphview()
|
.graphview()
|
||||||
.node(node_id)
|
.node(node_id)
|
||||||
.unwrap_or_else(|| panic!("Unable to retrieve node with id {}", node_id));
|
.unwrap_or_else(|| panic!("Unable to retrieve node with id {}", node_id));
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
|
||||||
fn port(&self, node_id: u32, port_id: u32) -> GM::Port {
|
pub fn port(&self, node_id: u32, port_id: u32) -> GM::Port {
|
||||||
let node = self.node(node_id);
|
let node = self.node(node_id);
|
||||||
node.port(port_id)
|
node.port(port_id)
|
||||||
.unwrap_or_else(|| panic!("Unable to retrieve port with id {}", port_id))
|
.unwrap_or_else(|| panic!("Unable to retrieve port with id {}", port_id))
|
||||||
|
@ -961,15 +718,16 @@ impl GPSApp {
|
||||||
_ => String::from("?"),
|
_ => String::from("?"),
|
||||||
};
|
};
|
||||||
let port_name = format!("{}{}", port_name, ports.len());
|
let port_name = format!("{}{}", port_name, ports.len());
|
||||||
let port = self
|
let port = graphbook::current_graphtab(self)
|
||||||
.session()
|
|
||||||
.graphview()
|
.graphview()
|
||||||
.create_port(&port_name, direction, presence);
|
.create_port(&port_name, direction, presence);
|
||||||
let id = port.id();
|
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) = self.session().graphview().node(node_id) {
|
if let Some(mut node) = graphbook::current_graphtab(self).graphview().node(node_id) {
|
||||||
self.session().graphview().add_port_to_node(&mut node, port);
|
graphbook::current_graphtab(self)
|
||||||
|
.graphview()
|
||||||
|
.add_port_to_node(&mut node, port);
|
||||||
}
|
}
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -981,37 +739,43 @@ impl GPSApp {
|
||||||
port_from_id: u32,
|
port_from_id: u32,
|
||||||
port_to_id: u32,
|
port_to_id: u32,
|
||||||
) {
|
) {
|
||||||
let session = self.session();
|
let graphtab = graphbook::current_graphtab(self);
|
||||||
let graphview = session.graphview.borrow();
|
let link =
|
||||||
let link = graphview.create_link(node_from_id, node_to_id, port_from_id, port_to_id);
|
graphtab
|
||||||
graphview.add_link(link);
|
.graphview()
|
||||||
|
.create_link(node_from_id, node_to_id, port_from_id, port_to_id);
|
||||||
|
graphtab.graphview().add_link(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_graph(&self) {
|
fn clear_graph(&self) {
|
||||||
self.session().graphview().clear();
|
graphbook::current_graphtab(self).graphview().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_graph(&self, filename: &str) -> anyhow::Result<()> {
|
pub fn save_graph(&self, filename: &str) -> anyhow::Result<()> {
|
||||||
let mut file = File::create(filename)?;
|
let mut file = File::create(filename)?;
|
||||||
let buffer = self.session().graphview().render_xml()?;
|
let buffer = graphbook::current_graphtab(self).graphview().render_xml()?;
|
||||||
file.write_all(&buffer)?;
|
file.write_all(&buffer)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_graph(&self, filename: &str) -> anyhow::Result<()> {
|
fn load_graph(&self, filename: &str, untitled: bool) -> anyhow::Result<()> {
|
||||||
let mut file = File::open(filename)?;
|
let mut file = File::open(filename)?;
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
file.read_to_end(&mut buffer).expect("buffer overflow");
|
file.read_to_end(&mut buffer).expect("buffer overflow");
|
||||||
self.session().graphview().load_from_xml(buffer)?;
|
let graphtab = graphbook::current_graphtab(self);
|
||||||
|
graphtab.graphview().load_from_xml(buffer)?;
|
||||||
|
if !untitled {
|
||||||
|
graphbook::current_graphtab_set_filename(self, filename);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_pipeline(&self, pipeline_desc: &str) -> anyhow::Result<()> {
|
fn load_pipeline(&self, pipeline_desc: &str) -> anyhow::Result<()> {
|
||||||
let session = self.session();
|
let graphtab = graphbook::current_graphtab(self);
|
||||||
let player = session.player();
|
graphtab
|
||||||
let graphview = session.graphview.borrow();
|
.player()
|
||||||
player.graphview_from_pipeline_description(&graphview, pipeline_desc);
|
.graphview_from_pipeline_description(&graphtab.graphview(), pipeline_desc);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,6 +199,7 @@ impl Player {
|
||||||
PipelineState::Paused => pipeline.set_state(gst::State::Paused)?,
|
PipelineState::Paused => pipeline.set_state(gst::State::Paused)?,
|
||||||
PipelineState::Stopped | PipelineState::Error => {
|
PipelineState::Stopped | PipelineState::Error => {
|
||||||
pipeline.set_state(gst::State::Null)?;
|
pipeline.set_state(gst::State::Null)?;
|
||||||
|
self.n_video_sink.set(0);
|
||||||
gst::StateChangeSuccess::Success
|
gst::StateChangeSuccess::Success
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -268,6 +269,9 @@ impl Player {
|
||||||
pub fn playing(&self) -> bool {
|
pub fn playing(&self) -> bool {
|
||||||
self.state() == PipelineState::Playing || self.state() == PipelineState::Paused
|
self.state() == PipelineState::Playing || self.state() == PipelineState::Paused
|
||||||
}
|
}
|
||||||
|
pub fn n_video_sink(&self) -> usize {
|
||||||
|
self.n_video_sink.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn downgrade(&self) -> PlayerWeak {
|
pub fn downgrade(&self) -> PlayerWeak {
|
||||||
PlayerWeak(Rc::downgrade(&self.0))
|
PlayerWeak(Rc::downgrade(&self.0))
|
||||||
|
|
488
src/graphbook.rs
Normal file
488
src/graphbook.rs
Normal file
|
@ -0,0 +1,488 @@
|
||||||
|
// graphbook.rs
|
||||||
|
//
|
||||||
|
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
|
||||||
|
//
|
||||||
|
// This file is part of GstPipelineStudio
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
use crate::app::{GPSApp, GPSAppWeak};
|
||||||
|
use crate::gps as GPS;
|
||||||
|
use crate::graphmanager as GM;
|
||||||
|
use crate::graphmanager::PropertyExt;
|
||||||
|
use crate::logger;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
use crate::ui as GPSUI;
|
||||||
|
use crate::{GPS_DEBUG, GPS_TRACE, GPS_WARN};
|
||||||
|
use glib::Value;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::{gio, glib, graphene};
|
||||||
|
use std::cell::{Cell, Ref, RefCell};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum TabState {
|
||||||
|
Undefined = 0,
|
||||||
|
Modified,
|
||||||
|
Saved,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GraphTab {
|
||||||
|
graphview: RefCell<GM::GraphView>,
|
||||||
|
player: RefCell<GPS::Player>,
|
||||||
|
id: Cell<u32>,
|
||||||
|
name: gtk::Label,
|
||||||
|
filename: RefCell<String>,
|
||||||
|
state: Cell<TabState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphTab {
|
||||||
|
pub fn new(app: GPSAppWeak, id: u32, filename: &str) -> Self {
|
||||||
|
let label = gtk::Label::new(Some("Untitled*"));
|
||||||
|
let graphtab = GraphTab {
|
||||||
|
id: Cell::new(id),
|
||||||
|
graphview: RefCell::new(GM::GraphView::new()),
|
||||||
|
player: RefCell::new(
|
||||||
|
GPS::Player::new().expect("Unable to initialize GStreamer subsystem"),
|
||||||
|
),
|
||||||
|
name: label,
|
||||||
|
filename: RefCell::new(filename.to_string()),
|
||||||
|
state: Cell::new(TabState::Undefined),
|
||||||
|
};
|
||||||
|
graphtab
|
||||||
|
.graphview
|
||||||
|
.borrow()
|
||||||
|
.set_id(graphbook_get_new_graphview_id(&app));
|
||||||
|
graphtab.player.borrow().set_app(app);
|
||||||
|
graphtab
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.id.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget_label(&self) -> >k::Label {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphview(&self) -> Ref<GM::GraphView> {
|
||||||
|
self.graphview.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player(&self) -> Ref<GPS::Player> {
|
||||||
|
self.player.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_name(&self, name: &str) {
|
||||||
|
self.name.set_text(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn basename(&self) -> String {
|
||||||
|
Path::new(&self.filename.borrow().as_str())
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_filename(&self, filename: &str) {
|
||||||
|
self.filename.replace(filename.to_string());
|
||||||
|
self.set_name(self.basename().as_str());
|
||||||
|
self.set_modified(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filename(&self) -> String {
|
||||||
|
self.filename.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_modified(&self, modified: bool) {
|
||||||
|
let name = self.basename();
|
||||||
|
if modified {
|
||||||
|
self.set_name(&(name + "*"));
|
||||||
|
self.state.set(TabState::Modified);
|
||||||
|
} else {
|
||||||
|
self.set_name(name.as_str());
|
||||||
|
self.state.set(TabState::Saved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undefined(&self) -> bool {
|
||||||
|
self.state.get() == TabState::Undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modified(&self) -> bool {
|
||||||
|
self.state.get() == TabState::Modified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphtab(app: &GPSApp, id: u32) -> GraphTab {
|
||||||
|
app.graphbook
|
||||||
|
.borrow()
|
||||||
|
.get(&id)
|
||||||
|
.cloned()
|
||||||
|
.expect("the current graphtab should be ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphbook_get_new_graphview_id(app_weak: &GPSAppWeak) -> u32 {
|
||||||
|
let app = app_weak.upgrade();
|
||||||
|
let mut graphview_id: u32 = 0;
|
||||||
|
for tab in app.unwrap().graphbook.borrow().values() {
|
||||||
|
if tab.graphview().id() > graphview_id {
|
||||||
|
graphview_id = tab.graphview().id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graphview_id + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphbook_get_new_graphtab_id(app: &GPSApp) -> u32 {
|
||||||
|
let mut graphtab_id: u32 = 0;
|
||||||
|
for tab in app.graphbook.borrow().values() {
|
||||||
|
if tab.id() > graphtab_id {
|
||||||
|
graphtab_id = tab.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graphtab_id + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_graphtab(app: &GPSApp) -> GraphTab {
|
||||||
|
graphtab(app, app.current_graphtab.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_graphtab_set_filename(app: &GPSApp, filename: &str) {
|
||||||
|
app.graphbook
|
||||||
|
.borrow()
|
||||||
|
.get(&app.current_graphtab.get())
|
||||||
|
.expect("the graphtab is available")
|
||||||
|
.set_filename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_graphtab_set_modified(app: &GPSApp, modified: bool) {
|
||||||
|
app.graphbook
|
||||||
|
.borrow()
|
||||||
|
.get(&app.current_graphtab.get())
|
||||||
|
.expect("the graphtab is available")
|
||||||
|
.set_modified(modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_graphbook(app: &GPSApp) {
|
||||||
|
let graphbook: gtk::Notebook = app
|
||||||
|
.builder
|
||||||
|
.object("graphbook")
|
||||||
|
.expect("Couldn't get the graphbook");
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
graphbook.connect_switch_page(move |_book, widget, page| {
|
||||||
|
let graphview = widget
|
||||||
|
.first_child()
|
||||||
|
.expect("Unable to get the child from the graphbook, ie the graphview");
|
||||||
|
if let Ok(graphview) = graphview.dynamic_cast::<GM::GraphView>() {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_TRACE!("graphview.id() {} graphbook page {}", graphview.id(), page);
|
||||||
|
app.current_graphtab.set(page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
|
||||||
|
let graph_tab = GraphTab::new(app.downgrade(), id, name.unwrap_or("Untitled"));
|
||||||
|
let gt = graph_tab.clone();
|
||||||
|
app.graphbook.borrow_mut().insert(id, graph_tab);
|
||||||
|
|
||||||
|
let graphbook: gtk::Notebook = app
|
||||||
|
.builder
|
||||||
|
.object("graphbook")
|
||||||
|
.expect("Couldn't get graphbook");
|
||||||
|
let drawing_area_window: gtk::Viewport = gtk::Viewport::builder().build();
|
||||||
|
drawing_area_window.set_child(Some(&*graphtab(app, id).graphview()));
|
||||||
|
|
||||||
|
let tab_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||||
|
let label = gt.widget_label();
|
||||||
|
tab_box.append(label);
|
||||||
|
let icon = gtk::Image::from_icon_name("window-close-symbolic");
|
||||||
|
let close_button = gtk::Button::builder().build();
|
||||||
|
close_button.set_child(Some(&icon));
|
||||||
|
close_button.add_css_class("small-button");
|
||||||
|
close_button.add_css_class("image-button");
|
||||||
|
close_button.add_css_class("flat");
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
close_button.connect_clicked(glib::clone!(@weak graphbook => move |_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
graphbook.remove_page(Some(current_graphtab(&app).id()));
|
||||||
|
}));
|
||||||
|
tab_box.append(&close_button);
|
||||||
|
graphbook.append_page(&drawing_area_window, Some(&tab_box));
|
||||||
|
graphbook.set_tab_reorderable(&drawing_area_window, true);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"graph-updated",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let id = values[1].get::<u32>().expect("id in args[1]");
|
||||||
|
GPS_DEBUG!("Graph updated id={}", id);
|
||||||
|
let _ = app
|
||||||
|
.save_graph(
|
||||||
|
Settings::default_graph_file_path()
|
||||||
|
.to_str()
|
||||||
|
.expect("Unable to convert to string"),
|
||||||
|
)
|
||||||
|
.map_err(|e| GPS_WARN!("Unable to save file {}", e));
|
||||||
|
current_graphtab_set_modified(&app, true);
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"node-added",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
||||||
|
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
||||||
|
GPS_DEBUG!("Node added node id={} in graph id={}", node_id, graph_id);
|
||||||
|
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
|
||||||
|
let description = GPS::ElementInfo::element_description(&node.name()).ok();
|
||||||
|
node.set_tooltip_markup(description.as_deref());
|
||||||
|
for port in node.all_ports(GM::PortDirection::All) {
|
||||||
|
let caps = PropertyExt::property(&port,"_caps");
|
||||||
|
GPS_DEBUG!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
||||||
|
port.set_tooltip_markup(caps.as_deref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"port-added",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let graph_id = values[1].get::<u32>().expect("graph id in args[1]");
|
||||||
|
let node_id = values[2].get::<u32>().expect("node id in args[2]");
|
||||||
|
let port_id = values[3].get::<u32>().expect("port id in args[3]");
|
||||||
|
GPS_DEBUG!("Port added port id={} to node id={} in graph id={}", port_id, node_id, graph_id);
|
||||||
|
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
|
||||||
|
if let Some(port) = node.port(port_id) {
|
||||||
|
let caps = PropertyExt::property(&port, "_caps");
|
||||||
|
GPS_DEBUG!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
|
||||||
|
port.set_tooltip_markup(caps.as_deref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// When user clicks on port with right button
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview()
|
||||||
|
.connect_local(
|
||||||
|
"graph-right-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let point = values[1].get::<graphene::Point>().expect("point in args[2]");
|
||||||
|
let pop_menu = app.app_pop_menu_at_position(&*current_graphtab(&app).graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
||||||
|
let menu: gio::MenuModel = app
|
||||||
|
.builder
|
||||||
|
.object("graph_menu")
|
||||||
|
.expect("Couldn't graph_menu");
|
||||||
|
pop_menu.set_menu_model(Some(&menu));
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("graph.clear",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
current_graphtab(&app).graphview().clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("graph.check",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
let render_parse_launch = current_graphtab(&app).player().pipeline_description_from_graphview(¤t_graphtab(&app).graphview());
|
||||||
|
if current_graphtab(&app).player().create_pipeline(&render_parse_launch).is_ok() {
|
||||||
|
GPSUI::message::display_message_dialog(&render_parse_launch,gtk::MessageType::Info, |_| {});
|
||||||
|
} else {
|
||||||
|
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{render_parse_launch}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("graph.pipeline_details",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPSUI::properties::display_pipeline_details(&app);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
pop_menu.show();
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When user clicks on port with right button
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview()
|
||||||
|
.connect_local("port-right-clicked", false, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let port_id = values[1].get::<u32>().expect("port id args[1]");
|
||||||
|
let node_id = values[2].get::<u32>().expect("node id args[2]");
|
||||||
|
let point = values[3]
|
||||||
|
.get::<graphene::Point>()
|
||||||
|
.expect("point in args[3]");
|
||||||
|
let pop_menu = app.app_pop_menu_at_position(
|
||||||
|
&*current_graphtab(&app).graphview(),
|
||||||
|
point.to_vec2().x() as f64,
|
||||||
|
point.to_vec2().y() as f64,
|
||||||
|
);
|
||||||
|
let menu: gio::MenuModel = app
|
||||||
|
.builder
|
||||||
|
.object("port_menu")
|
||||||
|
.expect("Couldn't get menu model for port");
|
||||||
|
pop_menu.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
|
if current_graphtab(&app)
|
||||||
|
.graphview()
|
||||||
|
.can_remove_port(node_id, port_id)
|
||||||
|
{
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("port.delete", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("port.delete-link port id {} node id {}", port_id, node_id);
|
||||||
|
current_graphtab(&app)
|
||||||
|
.graphview()
|
||||||
|
.remove_port(node_id, port_id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
app.disconnect_app_menu_action("port.delete");
|
||||||
|
}
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("port.properties", move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("port.properties port id {} node id {}", port_id, node_id);
|
||||||
|
let node = app.node(node_id);
|
||||||
|
let port = app.port(node_id, port_id);
|
||||||
|
GPSUI::properties::display_pad_properties(
|
||||||
|
&app,
|
||||||
|
&node.name(),
|
||||||
|
&port.name(),
|
||||||
|
node_id,
|
||||||
|
port_id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
pop_menu.show();
|
||||||
|
None
|
||||||
|
});
|
||||||
|
|
||||||
|
// When user clicks on node with right button
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview()
|
||||||
|
.connect_local(
|
||||||
|
"node-right-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
||||||
|
let point = values[2].get::<graphene::Point>().expect("point in args[2]");
|
||||||
|
let pop_menu = app.app_pop_menu_at_position(&*current_graphtab(&app).graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
|
||||||
|
let menu: gio::MenuModel = app
|
||||||
|
.builder
|
||||||
|
.object("node_menu")
|
||||||
|
.expect("Couldn't get menu model for node");
|
||||||
|
pop_menu.set_menu_model(Some(&menu));
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.add-to-favorite",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.add-to-favorite id: {}", node_id);
|
||||||
|
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
|
||||||
|
GPSUI::elements::add_to_favorite_list(&app, node.name());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.delete",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.delete id: {}", node_id);
|
||||||
|
current_graphtab(&app).graphview().remove_node(node_id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let node = app.node(node_id);
|
||||||
|
if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) {
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.request-pad-input",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.request-pad-input id: {}", node_id);
|
||||||
|
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
app.disconnect_app_menu_action("node.request-pad-input");
|
||||||
|
}
|
||||||
|
let node = app.node(node_id);
|
||||||
|
if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) {
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.request-pad-output",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.request-pad-output id: {}", node_id);
|
||||||
|
app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
app.disconnect_app_menu_action("node.request-pad-output");
|
||||||
|
}
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
app.connect_app_menu_action("node.properties",
|
||||||
|
move |_,_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
GPS_DEBUG!("node.properties id {}", node_id);
|
||||||
|
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
|
||||||
|
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
pop_menu.show();
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"node-double-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let node_id = values[1].get::<u32>().expect("node id args[1]");
|
||||||
|
GPS_TRACE!("Node double clicked id={}", node_id);
|
||||||
|
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
|
||||||
|
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let app_weak = app.downgrade();
|
||||||
|
gt.graphview().connect_local(
|
||||||
|
"link-double-clicked",
|
||||||
|
false,
|
||||||
|
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
|
||||||
|
let app = upgrade_weak!(app_weak, None);
|
||||||
|
let link_id = values[1].get::<u32>().expect("link id args[1]");
|
||||||
|
GPS_TRACE!("link double clicked id={}", link_id);
|
||||||
|
let link = current_graphtab(&app).graphview().link(link_id).unwrap();
|
||||||
|
GPSUI::dialog::create_input_dialog(
|
||||||
|
"Enter caps filter description",
|
||||||
|
"description",
|
||||||
|
&link.name(),
|
||||||
|
&app,
|
||||||
|
move |app, link_desc| {
|
||||||
|
current_graphtab(&app).graphview().set_link_name(link.id(), link_desc.as_str());
|
||||||
|
GPS_DEBUG!("link double clicked id={} name={}", link.id(), link.name());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,8 +11,8 @@ mod macros;
|
||||||
mod app;
|
mod app;
|
||||||
mod common;
|
mod common;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod graphbook;
|
||||||
mod graphmanager;
|
mod graphmanager;
|
||||||
mod session;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod logger;
|
mod logger;
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
// main.rs
|
|
||||||
//
|
|
||||||
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
|
|
||||||
//
|
|
||||||
// This file is part of GstPipelineStudio
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
use crate::app::GPSAppWeak;
|
|
||||||
use crate::gps as GPS;
|
|
||||||
use crate::graphmanager as GM;
|
|
||||||
use std::cell::Ref;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GPSSession {
|
|
||||||
pub graphview: RefCell<GM::GraphView>,
|
|
||||||
pub player: RefCell<GPS::Player>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GPSSession {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
GPSSession {
|
|
||||||
graphview: RefCell::new(GM::GraphView::new()),
|
|
||||||
player: RefCell::new(
|
|
||||||
GPS::Player::new().expect("Unable to initialize GStreamer subsystem"),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn graphview(&self) -> Ref<GM::GraphView> {
|
|
||||||
self.graphview.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_graphview_id(&self, id: u32) {
|
|
||||||
self.graphview.borrow().set_id(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn player(&self) -> Ref<GPS::Player> {
|
|
||||||
self.player.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_player(&self, app: GPSAppWeak) {
|
|
||||||
self.player.borrow().set_app(app);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,17 +11,21 @@
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the graph">_Open</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the graph">_Open</attribute>
|
||||||
<attribute name="action">app.open</attribute>
|
<attribute name="action">app.open</attribute>
|
||||||
<attribute name="accel"><primary>n</attribute>
|
<attribute name="accel"><primary>o</attribute>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that opens a pipeline">_Open pipeline</attribute>
|
<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="action">app.open_pipeline</attribute>
|
||||||
<attribute name="accel"><primary>p</attribute>
|
<attribute name="accel"><primary>p</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save</attribute>
|
||||||
|
<attribute name="action">app.save</attribute>
|
||||||
|
<attribute name="accel"><primary>s</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>
|
||||||
<attribute name="accel"><primary>n</attribute>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that open the preferences">_Preferences</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that open the preferences">_Preferences</attribute>
|
||||||
|
@ -48,6 +52,10 @@
|
||||||
</menu>
|
</menu>
|
||||||
<menu id="graph_menu">
|
<menu id="graph_menu">
|
||||||
<section>
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="graph menu entry clear graph">_Clear graph</attribute>
|
||||||
|
<attribute name="action">app.graph.clear</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="graph menu entry check graph">_Check graph</attribute>
|
<attribute name="label" translatable="yes" comments="graph menu entry check graph">_Check graph</attribute>
|
||||||
<attribute name="action">app.graph.check</attribute>
|
<attribute name="action">app.graph.check</attribute>
|
||||||
|
@ -205,7 +213,7 @@
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkViewport" id="drawing_area">
|
<object class="GtkNotebook" id="graphbook">
|
||||||
<child>
|
<child>
|
||||||
<placeholder/>
|
<placeholder/>
|
||||||
</child>
|
</child>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
use crate::app::GPSApp;
|
use crate::app::GPSApp;
|
||||||
use crate::common;
|
use crate::common;
|
||||||
use crate::gps as GPS;
|
use crate::gps as GPS;
|
||||||
|
use crate::graphbook;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
use crate::ui as GPSUI;
|
use crate::ui as GPSUI;
|
||||||
use crate::{GPS_INFO, GPS_TRACE};
|
use crate::{GPS_INFO, GPS_TRACE};
|
||||||
|
@ -300,7 +301,10 @@ pub fn display_pipeline_details(app: &GPSApp) {
|
||||||
grid.set_row_spacing(4);
|
grid.set_row_spacing(4);
|
||||||
grid.set_margin_bottom(12);
|
grid.set_margin_bottom(12);
|
||||||
|
|
||||||
if let Some(elements) = app.session().player().pipeline_elements() {
|
if let Some(elements) = graphbook::current_graphtab(app)
|
||||||
|
.player()
|
||||||
|
.pipeline_elements()
|
||||||
|
{
|
||||||
let elements_list = elements.join(" ");
|
let elements_list = elements.join(" ");
|
||||||
let label = gtk::Label::builder()
|
let label = gtk::Label::builder()
|
||||||
.label(format!("{} elements:", elements.len()))
|
.label(format!("{} elements:", elements.len()))
|
||||||
|
|
Loading…
Reference in a new issue