2021-11-19 11:34:19 +00:00
|
|
|
// app.rs
|
|
|
|
//
|
|
|
|
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use gtk::cairo::Context;
|
|
|
|
use gtk::prelude::*;
|
2021-11-23 17:04:57 +00:00
|
|
|
use gtk::{gio, glib};
|
2021-11-19 11:34:19 +00:00
|
|
|
use gtk::{
|
2021-11-23 17:04:57 +00:00
|
|
|
AboutDialog, Application, ApplicationWindow, Builder, Button, DrawingArea, FileChooserDialog,
|
|
|
|
ResponseType, Statusbar, Viewport,
|
2021-11-19 11:34:19 +00:00
|
|
|
};
|
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::rc::{Rc, Weak};
|
|
|
|
use std::{error, ops};
|
|
|
|
|
2021-11-19 16:51:06 +00:00
|
|
|
use crate::graph::{Element, Graph};
|
2021-11-19 11:34:19 +00:00
|
|
|
use crate::pipeline::Pipeline;
|
2021-11-22 12:08:05 +00:00
|
|
|
use crate::pluginlist;
|
2021-11-19 11:34:19 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct GPSAppInner {
|
|
|
|
pub window: gtk::ApplicationWindow,
|
2021-11-19 16:51:06 +00:00
|
|
|
pub drawing_area: DrawingArea,
|
2021-11-19 11:34:19 +00:00
|
|
|
pub builder: Builder,
|
2021-11-19 16:51:06 +00:00
|
|
|
pub pipeline: RefCell<Pipeline>,
|
|
|
|
pub graph: RefCell<Graph>,
|
2021-11-19 11:34:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This represents our main application window.
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct GPSApp(Rc<GPSAppInner>);
|
|
|
|
|
|
|
|
// Deref into the contained struct to make usage a bit more ergonomic
|
|
|
|
impl ops::Deref for GPSApp {
|
|
|
|
type Target = GPSAppInner;
|
|
|
|
|
|
|
|
fn deref(&self) -> &GPSAppInner {
|
|
|
|
&*self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Weak reference to our application struct
|
|
|
|
//
|
|
|
|
// Weak references are important to prevent reference cycles. Reference cycles are cases where
|
|
|
|
// struct A references directly or indirectly struct B, and struct B references struct A again
|
|
|
|
// while both are using reference counting.
|
|
|
|
pub struct GPSAppWeak(Weak<GPSAppInner>);
|
|
|
|
impl GPSAppWeak {
|
|
|
|
// Upgrade to a strong reference if it still exists
|
|
|
|
pub fn upgrade(&self) -> Option<GPSApp> {
|
|
|
|
self.0.upgrade().map(GPSApp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_elements(elements: &Vec<Element>, c: &Context) {
|
|
|
|
for element in elements {
|
|
|
|
c.rectangle(element.position.0, element.position.1, 80.0, 45.0);
|
|
|
|
c.fill().expect("Can not draw into context");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GPSApp {
|
|
|
|
fn new(application: >k::Application) -> anyhow::Result<GPSApp, Box<dyn error::Error>> {
|
|
|
|
let glade_src = include_str!("gps.ui");
|
|
|
|
let builder = Builder::from_string(glade_src);
|
|
|
|
let window: ApplicationWindow = builder.object("mainwindow").expect("Couldn't get window");
|
|
|
|
window.set_application(Some(application));
|
2021-11-23 17:04:57 +00:00
|
|
|
window.set_title(Some("GstPipelineStudio"));
|
2021-11-19 11:34:19 +00:00
|
|
|
window.set_size_request(800, 600);
|
|
|
|
let pipeline = Pipeline::new().expect("Unable to initialize the pipeline");
|
2021-11-19 16:51:06 +00:00
|
|
|
let drawing_area = DrawingArea::new();
|
2021-11-19 11:34:19 +00:00
|
|
|
let app = GPSApp(Rc::new(GPSAppInner {
|
|
|
|
window,
|
2021-11-19 16:51:06 +00:00
|
|
|
drawing_area,
|
2021-11-19 11:34:19 +00:00
|
|
|
builder,
|
2021-11-19 16:51:06 +00:00
|
|
|
pipeline: RefCell::new(pipeline),
|
|
|
|
graph: RefCell::new(Graph::default()),
|
2021-11-19 11:34:19 +00:00
|
|
|
}));
|
|
|
|
Ok(app)
|
|
|
|
}
|
|
|
|
pub fn on_startup(application: >k::Application) {
|
|
|
|
// Create application and error out if that fails for whatever reason
|
|
|
|
let app = match GPSApp::new(application) {
|
|
|
|
Ok(app) => app,
|
|
|
|
Err(_err) => {
|
|
|
|
/* utils::show_error_dialog(
|
|
|
|
true,
|
|
|
|
format!("Error creating application: {}", err).as_str(),
|
|
|
|
); */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// When the application is activated show the UI. This happens when the first process is
|
|
|
|
// started, and in the first process whenever a second process is started
|
|
|
|
let app_weak = app.downgrade();
|
2021-11-23 17:04:57 +00:00
|
|
|
|
|
|
|
application.connect_activate(glib::clone!(@weak application => move |_| {
|
2021-11-19 11:34:19 +00:00
|
|
|
let app = upgrade_weak!(app_weak);
|
2021-11-23 17:04:57 +00:00
|
|
|
app.build_ui(&application);
|
|
|
|
}));
|
2021-11-19 11:34:19 +00:00
|
|
|
|
|
|
|
let app_container = RefCell::new(Some(app));
|
|
|
|
application.connect_shutdown(move |_| {
|
|
|
|
let app = app_container
|
|
|
|
.borrow_mut()
|
|
|
|
.take()
|
|
|
|
.expect("Shutdown called multiple times");
|
|
|
|
app.drop();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-11-23 17:04:57 +00:00
|
|
|
pub fn build_ui(&self, application: &Application) {
|
2021-11-19 16:51:06 +00:00
|
|
|
let app_weak = self.downgrade();
|
2021-11-23 17:04:57 +00:00
|
|
|
let drawing_area = gtk::DrawingArea::builder()
|
|
|
|
.content_height(24)
|
|
|
|
.content_width(24)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
drawing_area.set_draw_func(move |_, c, width, height| {
|
|
|
|
let app = upgrade_weak!(app_weak);
|
|
|
|
println!("w: {} h: {} c:{}", width, height, c);
|
2021-11-19 16:51:06 +00:00
|
|
|
let mut graph = app.graph.borrow_mut();
|
|
|
|
let elements = graph.elements();
|
|
|
|
draw_elements(&elements, c);
|
2021-11-23 17:04:57 +00:00
|
|
|
c.paint().expect("Invalid cairo surface state");
|
2021-11-19 11:34:19 +00:00
|
|
|
});
|
2021-11-23 17:04:57 +00:00
|
|
|
let drawing_area_window: Viewport = self
|
2021-11-19 11:34:19 +00:00
|
|
|
.builder
|
2021-11-23 17:04:57 +00:00
|
|
|
.object("drawing_area")
|
2021-11-19 11:34:19 +00:00
|
|
|
.expect("Couldn't get window");
|
2021-11-23 17:04:57 +00:00
|
|
|
drawing_area_window.set_child(Some(&drawing_area));
|
|
|
|
|
|
|
|
// let app_weak = self.downgrade();
|
|
|
|
// event_box.connect_button_release_event(move |_w, evt| {
|
|
|
|
// let app = upgrade_weak!(app_weak, gtk::Inhibit(false));
|
|
|
|
// let mut element: Element = Default::default();
|
|
|
|
// element.position.0 = evt.position().0;
|
|
|
|
// element.position.1 = evt.position().1;
|
|
|
|
// app.add_new_element(element);
|
|
|
|
// app.drawing_area.queue_draw();
|
|
|
|
// gtk::Inhibit(false)
|
|
|
|
// });
|
|
|
|
let window = &self.window;
|
|
|
|
|
|
|
|
window.show();
|
|
|
|
let status_bar: Statusbar = self
|
2021-11-19 11:34:19 +00:00
|
|
|
.builder
|
2021-11-23 17:04:57 +00:00
|
|
|
.object("status_bar")
|
2021-11-19 11:34:19 +00:00
|
|
|
.expect("Couldn't get window");
|
2021-11-23 17:04:57 +00:00
|
|
|
status_bar.push(status_bar.context_id("Description"), "GPS is ready");
|
|
|
|
|
|
|
|
let action = gio::SimpleAction::new("quit", None);
|
|
|
|
action.connect_activate({
|
|
|
|
let app = application.downgrade();
|
|
|
|
move |_, _| {
|
|
|
|
let app = app.upgrade().unwrap();
|
|
|
|
app.quit();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
application.add_action(&action);
|
|
|
|
application.set_accels_for_action("app.quit", &["<primary>q"]);
|
|
|
|
|
|
|
|
let action = gio::SimpleAction::new("new-window", None);
|
|
|
|
|
|
|
|
application.add_action(&action);
|
|
|
|
application.set_accels_for_action("app.new-window", &["<primary>n"]);
|
|
|
|
|
2021-11-19 11:34:19 +00:00
|
|
|
let about_dialog: AboutDialog = self
|
|
|
|
.builder
|
|
|
|
.object("dialog-about")
|
|
|
|
.expect("Couldn't get window");
|
2021-11-23 17:04:57 +00:00
|
|
|
let action = gio::SimpleAction::new("about", None);
|
|
|
|
action.connect_activate({
|
|
|
|
move |_, _| {
|
|
|
|
about_dialog.show();
|
|
|
|
}
|
2021-11-19 11:34:19 +00:00
|
|
|
});
|
2021-11-23 17:04:57 +00:00
|
|
|
application.add_action(&action);
|
|
|
|
application.set_accels_for_action("app.about", &["<primary>a"]);
|
2021-11-19 11:34:19 +00:00
|
|
|
|
|
|
|
// Create a dialog to select GStreamer elements.
|
|
|
|
let add_button: Button = self
|
|
|
|
.builder
|
|
|
|
.object("button-add-plugin")
|
|
|
|
.expect("Couldn't get app_button");
|
|
|
|
let app_weak = self.downgrade();
|
|
|
|
add_button.connect_clicked(glib::clone!(@weak window => move |_| {
|
|
|
|
let app = upgrade_weak!(app_weak);
|
|
|
|
let elements = Pipeline::elements_list().expect("Unable to obtain element's list");
|
2021-11-22 12:08:05 +00:00
|
|
|
pluginlist::display_plugin_list(&app, &elements);
|
2021-11-19 11:34:19 +00:00
|
|
|
}));
|
|
|
|
// Create a dialog to open a file
|
|
|
|
let open_button: Button = self
|
|
|
|
.builder
|
|
|
|
.object("button-open-file")
|
|
|
|
.expect("Couldn't get app_button");
|
|
|
|
let open_dialog: FileChooserDialog = self
|
|
|
|
.builder
|
|
|
|
.object("dialog-open-file")
|
|
|
|
.expect("Couldn't get window");
|
|
|
|
open_button.connect_clicked(glib::clone!(@weak window => move |_| {
|
|
|
|
// entry.set_text("Clicked!");
|
|
|
|
open_dialog.connect_response(|dialog, _| dialog.close());
|
|
|
|
open_dialog.add_buttons(&[
|
|
|
|
("Open", ResponseType::Ok),
|
|
|
|
("Cancel", ResponseType::Cancel)
|
|
|
|
]);
|
2021-11-23 17:04:57 +00:00
|
|
|
|
2021-11-19 11:34:19 +00:00
|
|
|
open_dialog.connect_response(|open_dialog, response| {
|
|
|
|
if response == ResponseType::Ok {
|
2021-11-23 17:04:57 +00:00
|
|
|
let file = open_dialog.file().expect("Couldn't get file");
|
|
|
|
println!("Files: {:?}", file);
|
2021-11-19 11:34:19 +00:00
|
|
|
}
|
|
|
|
open_dialog.close();
|
|
|
|
});
|
2021-11-23 17:04:57 +00:00
|
|
|
open_dialog.show();
|
2021-11-19 11:34:19 +00:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Downgrade to a weak reference
|
|
|
|
pub fn downgrade(&self) -> GPSAppWeak {
|
|
|
|
GPSAppWeak(Rc::downgrade(&self.0))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when the application shuts down. We drop our app struct here
|
|
|
|
fn drop(self) {}
|
2021-11-19 16:51:06 +00:00
|
|
|
|
|
|
|
pub fn add_new_element(&self, element: Element) {
|
|
|
|
self.graph.borrow_mut().add_element(element);
|
|
|
|
self.drawing_area.queue_draw();
|
|
|
|
}
|
2021-11-19 11:34:19 +00:00
|
|
|
}
|