GPS: introduce an app structure

This commit is contained in:
Stéphane Cerveau 2021-11-19 12:34:19 +01:00
parent 6351f1f43f
commit 05749a7009
6 changed files with 264 additions and 123 deletions

238
src/app.rs Normal file
View 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: &gtk::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: &gtk::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
View 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, ())
};
}

View file

@ -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();
} }

View file

@ -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: &gtk::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);
}

View file

@ -37,6 +37,7 @@ impl Default for ElementInfo {
} }
} }
#[derive(Debug)]
pub struct Pipeline { pub struct Pipeline {
initialized: bool, initialized: bool,
} }

View file

@ -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)],
); );