mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-25 18:41:03 +00:00
GPS: introduce an app structure
This commit is contained in:
parent
6351f1f43f
commit
05749a7009
6 changed files with 264 additions and 123 deletions
238
src/app.rs
Normal file
238
src/app.rs
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
// 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::glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::{
|
||||||
|
AboutDialog, AccelFlags, AccelGroup, ApplicationWindow, Builder, Button, DrawingArea, EventBox,
|
||||||
|
FileChooserDialog, MenuItem, ResponseType, Viewport, WindowPosition,
|
||||||
|
};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::{error, ops};
|
||||||
|
|
||||||
|
use crate::pipeline::Pipeline;
|
||||||
|
use crate::pluginlistwindow;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GPSAppInner {
|
||||||
|
pub window: gtk::ApplicationWindow,
|
||||||
|
pub builder: Builder,
|
||||||
|
pipeline: Pipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct Element {
|
||||||
|
name: String,
|
||||||
|
position: (f64, f64),
|
||||||
|
size: (f64, f64),
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
window.set_title("GstPipelineStudio");
|
||||||
|
window.set_position(WindowPosition::Center);
|
||||||
|
window.set_size_request(800, 600);
|
||||||
|
let pipeline = Pipeline::new().expect("Unable to initialize the pipeline");
|
||||||
|
|
||||||
|
let app = GPSApp(Rc::new(GPSAppInner {
|
||||||
|
window,
|
||||||
|
builder,
|
||||||
|
pipeline,
|
||||||
|
}));
|
||||||
|
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();
|
||||||
|
application.connect_activate(move |_| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
app.build_ui();
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_ui(&self) {
|
||||||
|
let drawing_area = DrawingArea::new();
|
||||||
|
let view_port: Viewport = self
|
||||||
|
.builder
|
||||||
|
.object("drawing_area")
|
||||||
|
.expect("Couldn't get window");
|
||||||
|
let event_box = EventBox::new();
|
||||||
|
event_box.add(&drawing_area);
|
||||||
|
view_port.add(&event_box);
|
||||||
|
let elements: Rc<RefCell<Vec<Element>>> = Rc::new(RefCell::new(vec![]));
|
||||||
|
let e_clone = elements.clone();
|
||||||
|
drawing_area.connect_draw(move |w, c| {
|
||||||
|
println!("w: {} c:{} e: {:?}", w, c, e_clone);
|
||||||
|
draw_elements(&e_clone.borrow().to_vec(), c);
|
||||||
|
gtk::Inhibit(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
event_box.connect_button_release_event(move |_w, evt| {
|
||||||
|
let mut elements = elements.borrow_mut();
|
||||||
|
let mut element: Element = Default::default();
|
||||||
|
element.position.0 = evt.position().0;
|
||||||
|
element.position.1 = evt.position().1;
|
||||||
|
elements.push(element);
|
||||||
|
drawing_area.queue_draw();
|
||||||
|
gtk::Inhibit(false)
|
||||||
|
});
|
||||||
|
let window = &self.window;
|
||||||
|
|
||||||
|
window.show_all();
|
||||||
|
|
||||||
|
let quit: MenuItem = self
|
||||||
|
.builder
|
||||||
|
.object("menu-quit")
|
||||||
|
.expect("Couldn't get window");
|
||||||
|
let about: MenuItem = self
|
||||||
|
.builder
|
||||||
|
.object("menu-about")
|
||||||
|
.expect("Couldn't get window");
|
||||||
|
let about_dialog: AboutDialog = self
|
||||||
|
.builder
|
||||||
|
.object("dialog-about")
|
||||||
|
.expect("Couldn't get window");
|
||||||
|
about.connect_activate(move |_| {
|
||||||
|
about_dialog.connect_delete_event(|dialog, _| {
|
||||||
|
dialog.hide();
|
||||||
|
gtk::Inhibit(true)
|
||||||
|
});
|
||||||
|
|
||||||
|
about_dialog.show_all();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
pluginlistwindow::build_plugin_list(&app, &elements);
|
||||||
|
}));
|
||||||
|
// 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)
|
||||||
|
]);
|
||||||
|
open_dialog.set_select_multiple(true);
|
||||||
|
open_dialog.connect_response(|open_dialog, response| {
|
||||||
|
if response == ResponseType::Ok {
|
||||||
|
let files = open_dialog.filenames();
|
||||||
|
println!("Files: {:?}", files);
|
||||||
|
}
|
||||||
|
open_dialog.close();
|
||||||
|
});
|
||||||
|
open_dialog.show_all();
|
||||||
|
}));
|
||||||
|
|
||||||
|
let accel_group = AccelGroup::new();
|
||||||
|
window.add_accel_group(&accel_group);
|
||||||
|
|
||||||
|
quit.connect_activate(glib::clone!(@weak window => move |_| {
|
||||||
|
window.close();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// `Primary` is `Ctrl` on Windows and Linux, and `command` on macOS
|
||||||
|
// It isn't available directly through `gdk::ModifierType`, since it has
|
||||||
|
// different values on different platforms.
|
||||||
|
let (key, modifier) = gtk::accelerator_parse("<Primary>Q");
|
||||||
|
quit.add_accelerator("activate", &accel_group, key, modifier, AccelFlags::VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {}
|
||||||
|
}
|
14
src/macros.rs
Normal file
14
src/macros.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Macro for upgrading a weak reference or returning the given value
|
||||||
|
//
|
||||||
|
// This works for glib/gtk objects as well as anything else providing an upgrade method
|
||||||
|
macro_rules! upgrade_weak {
|
||||||
|
($x:ident, $r:expr) => {{
|
||||||
|
match $x.upgrade() {
|
||||||
|
Some(o) => o,
|
||||||
|
None => return $r,
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
($x:ident) => {
|
||||||
|
upgrade_weak!($x, ())
|
||||||
|
};
|
||||||
|
}
|
10
src/main.rs
10
src/main.rs
|
@ -17,12 +17,15 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
mod mainwindow;
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
mod app;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod pluginlistwindow;
|
mod pluginlistwindow;
|
||||||
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
|
use crate::app::GPSApp;
|
||||||
fn main() {
|
fn main() {
|
||||||
// gio::resources_register_include!("compiled.gresource").unwrap();
|
// gio::resources_register_include!("compiled.gresource").unwrap();
|
||||||
|
|
||||||
|
@ -30,8 +33,9 @@ fn main() {
|
||||||
Some("com.github.gtk-rs.examples.menu_bar"),
|
Some("com.github.gtk-rs.examples.menu_bar"),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
|
application.connect_startup(|application| {
|
||||||
application.connect_activate(mainwindow::build_ui);
|
GPSApp::on_startup(application);
|
||||||
|
});
|
||||||
|
|
||||||
application.run();
|
application.run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
use gtk::cairo::Context;
|
|
||||||
use gtk::glib;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use gtk::{
|
|
||||||
AboutDialog, AccelFlags, AccelGroup, ApplicationWindow, Builder, Button, Dialog, DrawingArea,
|
|
||||||
EventBox, FileChooserDialog, MenuItem, ResponseType, Viewport, WindowPosition,
|
|
||||||
};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::pipeline::Pipeline;
|
|
||||||
use crate::pluginlistwindow;
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
struct Element {
|
|
||||||
name: String,
|
|
||||||
position: (f64, f64),
|
|
||||||
size: (f64, f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_ui(application: >k::Application) {
|
|
||||||
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));
|
|
||||||
|
|
||||||
window.set_title("GstPipelineStudio");
|
|
||||||
window.set_position(WindowPosition::Center);
|
|
||||||
window.set_size_request(800, 600);
|
|
||||||
|
|
||||||
let drawing_area = DrawingArea::new();
|
|
||||||
let view_port: Viewport = builder.object("drawing_area").expect("Couldn't get window");
|
|
||||||
let event_box = EventBox::new();
|
|
||||||
event_box.add(&drawing_area);
|
|
||||||
view_port.add(&event_box);
|
|
||||||
let elements: Rc<RefCell<Vec<Element>>> = Rc::new(RefCell::new(vec![]));
|
|
||||||
let e_clone = elements.clone();
|
|
||||||
drawing_area.connect_draw(move |w, c| {
|
|
||||||
println!("w: {} c:{} e: {:?}", w, c, e_clone);
|
|
||||||
draw_elements(&e_clone.borrow().to_vec(), c);
|
|
||||||
gtk::Inhibit(false)
|
|
||||||
});
|
|
||||||
|
|
||||||
event_box.connect_button_release_event(move |_w, evt| {
|
|
||||||
let mut elements = elements.borrow_mut();
|
|
||||||
let mut element: Element = Default::default();
|
|
||||||
element.position.0 = evt.position().0;
|
|
||||||
element.position.1 = evt.position().1;
|
|
||||||
elements.push(element);
|
|
||||||
drawing_area.queue_draw();
|
|
||||||
gtk::Inhibit(false)
|
|
||||||
});
|
|
||||||
window.show_all();
|
|
||||||
|
|
||||||
let quit: MenuItem = builder.object("menu-quit").expect("Couldn't get window");
|
|
||||||
let about: MenuItem = builder.object("menu-about").expect("Couldn't get window");
|
|
||||||
let about_dialog: AboutDialog = builder.object("dialog-about").expect("Couldn't get window");
|
|
||||||
about.connect_activate(move |_| {
|
|
||||||
about_dialog.show();
|
|
||||||
});
|
|
||||||
// Create a dialog to select GStreamer elements.
|
|
||||||
let add_button: Button = builder
|
|
||||||
.object("button-add-plugin")
|
|
||||||
.expect("Couldn't get app_button");
|
|
||||||
|
|
||||||
add_button.connect_clicked(glib::clone!(@weak window => move |_| {
|
|
||||||
// entry.set_text("Clicked!");
|
|
||||||
let pipeline = Pipeline::new();
|
|
||||||
let elements = Pipeline::get_elements_list().expect("cocuou");
|
|
||||||
pluginlistwindow::build_plugin_list(&window, &elements);
|
|
||||||
}));
|
|
||||||
// Create a dialog to open a file
|
|
||||||
let open_button: Button = builder
|
|
||||||
.object("button-open-file")
|
|
||||||
.expect("Couldn't get app_button");
|
|
||||||
let open_dialog: FileChooserDialog = 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)
|
|
||||||
]);
|
|
||||||
open_dialog.set_select_multiple(true);
|
|
||||||
open_dialog.connect_response(|open_dialog, response| {
|
|
||||||
if response == ResponseType::Ok {
|
|
||||||
let files = open_dialog.filenames();
|
|
||||||
println!("Files: {:?}", files);
|
|
||||||
}
|
|
||||||
open_dialog.close();
|
|
||||||
});
|
|
||||||
open_dialog.show_all();
|
|
||||||
}));
|
|
||||||
|
|
||||||
let accel_group = AccelGroup::new();
|
|
||||||
window.add_accel_group(&accel_group);
|
|
||||||
|
|
||||||
quit.connect_activate(glib::clone!(@weak window => move |_| {
|
|
||||||
window.close();
|
|
||||||
}));
|
|
||||||
|
|
||||||
// `Primary` is `Ctrl` on Windows and Linux, and `command` on macOS
|
|
||||||
// It isn't available directly through `gdk::ModifierType`, since it has
|
|
||||||
// different values on different platforms.
|
|
||||||
let (key, modifier) = gtk::accelerator_parse("<Primary>Q");
|
|
||||||
quit.add_accelerator("activate", &accel_group, key, modifier, AccelFlags::VISIBLE);
|
|
||||||
}
|
|
|
@ -37,6 +37,7 @@ impl Default for ElementInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::app::GPSApp;
|
||||||
use crate::pipeline::ElementInfo;
|
use crate::pipeline::ElementInfo;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib::{self, clone},
|
glib::{self, clone},
|
||||||
|
@ -6,8 +7,7 @@ use gtk::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
ApplicationWindow, CellRendererText, Dialog, Label, ListStore, Orientation, TreeView,
|
CellRendererText, Label, ListStore, Orientation, TreeView, TreeViewColumn, WindowPosition,
|
||||||
TreeViewColumn, WindowPosition,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn create_and_fill_model(elements: &Vec<ElementInfo>) -> ListStore {
|
fn create_and_fill_model(elements: &Vec<ElementInfo>) -> ListStore {
|
||||||
|
@ -45,10 +45,10 @@ fn create_and_setup_view() -> TreeView {
|
||||||
tree
|
tree
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_plugin_list(window: &ApplicationWindow, elements: &Vec<ElementInfo>) {
|
pub fn build_plugin_list(app: &GPSApp, elements: &Vec<ElementInfo>) {
|
||||||
let dialog = gtk::Dialog::with_buttons(
|
let dialog = gtk::Dialog::with_buttons(
|
||||||
Some("Edit Item"),
|
Some("Edit Item"),
|
||||||
Some(window),
|
Some(&app.window),
|
||||||
gtk::DialogFlags::MODAL,
|
gtk::DialogFlags::MODAL,
|
||||||
&[("Close", ResponseType::Close)],
|
&[("Close", ResponseType::Close)],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue