ui: introduce a new module

- Cleanup up app.rs and use the new module ui
- Remove display_plugin_list
- Can now render a graph and get the
parse launch line.
This commit is contained in:
Stéphane Cerveau 2022-01-20 15:06:14 +01:00
parent 71e68b846f
commit a2503ce86b
13 changed files with 476 additions and 460 deletions

View file

@ -21,12 +21,11 @@ use glib::SignalHandlerId;
use glib::Value;
use gtk::gdk::Rectangle;
use gtk::prelude::*;
use gtk::{
gdk::BUTTON_SECONDARY, Application, ApplicationWindow, Box, Builder, Button, CellRendererText,
FileChooserAction, FileChooserDialog, Label, ListStore, Paned, PopoverMenu, ResponseType,
Statusbar, TreeView, TreeViewColumn, Viewport, Widget,
};
use gtk::{gio, gio::SimpleAction, glib, graphene};
use gtk::{
Application, ApplicationWindow, Builder, Button, FileChooserAction, FileChooserDialog, Paned,
PopoverMenu, ResponseType, Statusbar, Viewport, Widget,
};
use log::error;
use once_cell::unsync::OnceCell;
use std::cell::RefCell;
@ -34,11 +33,11 @@ use std::collections::HashMap;
use std::ops;
use std::rc::{Rc, Weak};
use crate::about;
use crate::gps::{ElementInfo, PadInfo, Pipeline, PipelineState};
use crate::logger;
use crate::plugindialogs;
use crate::settings::Settings;
use crate::ui as GPSUI;
use crate::{GPS_DEBUG, GPS_ERROR, GPS_INFO, GPS_TRACE, GPS_WARN};
use crate::graphmanager::{GraphView, PortDirection, PortPresence};
@ -209,7 +208,7 @@ impl GPSApp {
application.add_action(&gio::SimpleAction::new("favorite.remove", None));
application.add_action(&gio::SimpleAction::new("graph.add-plugin", None));
application.add_action(&gio::SimpleAction::new("graph.check", None));
application.add_action(&gio::SimpleAction::new("port.delete", None));
@ -220,7 +219,12 @@ impl GPSApp {
application.add_action(&gio::SimpleAction::new("node.properties", None));
}
fn app_pop_menu_at_position(&self, widget: &impl IsA<Widget>, x: f64, y: f64) -> PopoverMenu {
pub fn app_pop_menu_at_position(
&self,
widget: &impl IsA<Widget>,
x: f64,
y: f64,
) -> PopoverMenu {
let mainwindow: ApplicationWindow = self
.builder
.object("mainwindow")
@ -265,7 +269,7 @@ impl GPSApp {
}
}
fn connect_app_menu_action<
pub fn connect_app_menu_action<
F: Fn(&SimpleAction, std::option::Option<&glib::Variant>) + 'static,
>(
&self,
@ -331,219 +335,6 @@ impl GPSApp {
file_chooser.show();
}
pub fn show_error_dialog(fatal: bool, message: &str) {
let app = gio::Application::default()
.expect("No default application")
.downcast::<gtk::Application>()
.expect("Default application has wrong type");
let dialog = gtk::MessageDialog::new(
app.active_window().as_ref(),
gtk::DialogFlags::MODAL,
gtk::MessageType::Error,
gtk::ButtonsType::Ok,
message,
);
dialog.connect_response(move |dialog, _| {
let app = gio::Application::default().expect("No default application");
dialog.destroy();
if fatal {
app.quit();
}
});
dialog.set_resizable(false);
dialog.show();
}
fn add_column_to_treeview(&self, tree_name: &str, column_name: &str, column_n: i32) {
let treeview: TreeView = self
.builder
.object(tree_name)
.expect("Couldn't get tree_name");
let column = TreeViewColumn::new();
let cell = CellRendererText::new();
column.pack_start(&cell, true);
// Association of the view's column with the model's `id` column.
column.add_attribute(&cell, "text", column_n);
column.set_title(column_name);
treeview.append_column(&column);
}
fn reset_logger_list(&self, logger_list: &TreeView) {
let model = ListStore::new(&[
String::static_type(),
String::static_type(),
String::static_type(),
]);
logger_list.set_model(Some(&model));
}
fn setup_logger_list(&self) {
self.add_column_to_treeview("treeview-logger", "TIME", 0);
self.add_column_to_treeview("treeview-logger", "LEVEL", 1);
self.add_column_to_treeview("treeview-logger", "LOG", 2);
let logger_list: TreeView = self
.builder
.object("treeview-logger")
.expect("Couldn't get treeview-logger");
self.reset_logger_list(&logger_list);
}
fn add_to_logger_list(&self, log_entry: &str) {
let logger_list: TreeView = self
.builder
.object("treeview-logger")
.expect("Couldn't get treeview-logger");
if let Some(model) = logger_list.model() {
let list_store = model
.dynamic_cast::<ListStore>()
.expect("Could not cast to ListStore");
let log: Vec<&str> = log_entry.splitn(3, ' ').collect();
list_store.insert_with_values(None, &[(0, &log[0]), (1, &log[1]), (2, &log[2])]);
}
}
fn reset_favorite_list(&self, favorite_list: &TreeView) {
let model = ListStore::new(&[String::static_type()]);
favorite_list.set_model(Some(&model));
let favorites = Settings::get_favorites_list();
for favorite in favorites {
model.insert_with_values(None, &[(0, &favorite)]);
}
}
fn setup_favorite_list(&self) {
let favorite_list: TreeView = self
.builder
.object("treeview-favorites")
.expect("Couldn't get treeview-favorites");
self.add_column_to_treeview("treeview-favorites", "Name", 0);
self.reset_favorite_list(&favorite_list);
let app_weak = self.downgrade();
favorite_list.connect_row_activated(move |tree_view, _tree_path, _tree_column| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model.get::<String>(&iter, 0);
GPS_DEBUG!("{} selected", element_name);
app.add_new_element(&element_name);
}
});
let gesture = gtk::GestureClick::new();
gesture.set_button(0);
let app_weak = self.downgrade();
gesture.connect_pressed(
glib::clone!(@weak favorite_list => move |gesture, _n_press, x, y| {
let app = upgrade_weak!(app_weak);
if gesture.current_button() == BUTTON_SECONDARY {
let selection = favorite_list.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model
.get::<String>(&iter, 0);
GPS_DEBUG!("Element {} selected", element_name);
let pop_menu = app.app_pop_menu_at_position(&favorite_list, x, y);
let menu: gio::MenuModel = app
.builder
.object("fav_menu")
.expect("Couldn't get fav_menu model");
pop_menu.set_menu_model(Some(&menu));
let app_weak = app.downgrade();
app.connect_app_menu_action("favorite.remove",
move |_,_| {
let app = upgrade_weak!(app_weak);
Settings::remove_favorite(&element_name);
app.reset_favorite_list(&favorite_list);
}
);
pop_menu.show();
}
}
}),
);
favorite_list.add_controller(&gesture);
}
fn add_to_favorite_list(&self, element_name: String) {
let favorites = Settings::get_favorites_list();
if !favorites.contains(&element_name) {
let favorite_list: TreeView = self
.builder
.object("treeview-favorites")
.expect("Couldn't get treeview-favorites");
if let Some(model) = favorite_list.model() {
let list_store = model
.dynamic_cast::<ListStore>()
.expect("Could not cast to ListStore");
list_store.insert_with_values(None, &[(0, &element_name)]);
Settings::add_favorite(&element_name);
}
}
}
fn reset_elements_list(&self, elements_list: &TreeView) {
let model = ListStore::new(&[String::static_type()]);
elements_list.set_model(Some(&model));
let elements = ElementInfo::elements_list().expect("Unable to obtain element's list");
for element in elements {
model.insert_with_values(None, &[(0, &element.name)]);
}
}
fn setup_elements_list(&self) {
let tree: TreeView = self
.builder
.object("treeview-elements")
.expect("Couldn't get treeview-elements");
self.add_column_to_treeview("treeview-elements", "Name", 0);
self.reset_elements_list(&tree);
let app_weak = self.downgrade();
tree.connect_row_activated(move |tree_view, _tree_path, _tree_column| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model.get::<String>(&iter, 0);
GPS_DEBUG!("{} selected", element_name);
app.add_new_element(&element_name);
}
});
let app_weak = self.downgrade();
tree.connect_cursor_changed(move |tree_view| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model.get::<String>(&iter, 0);
let description = ElementInfo::element_description(&element_name)
.expect("Unable to get element description from GStreamer");
let box_property: Box = app
.builder
.object("box-property")
.expect("Couldn't get treeview-elements");
while let Some(child) = box_property.first_child() {
box_property.remove(&child);
}
let label = Label::new(Some(""));
label.set_hexpand(true);
label.set_halign(gtk::Align::Start);
label.set_margin_start(4);
label.set_markup(&description);
box_property.append(&label);
}
});
}
pub fn display_plugin_list(app: &GPSApp) {
let elements = ElementInfo::elements_list().expect("Unable to obtain element's list");
plugindialogs::display_plugin_list(app, &elements);
}
pub fn build_ui(&self, application: &Application) {
let drawing_area_window: Viewport = self
.builder
@ -561,10 +352,10 @@ impl GPSApp {
.to_str()
.expect("Unable to convert log file path to a string"),
);
self.setup_logger_list();
GPSUI::logger::setup_logger_list(self);
let _ = ready_rx.attach(None, move |msg: String| {
let app = upgrade_weak!(app_weak, glib::Continue(false));
app.add_to_logger_list(&msg);
GPSUI::logger::add_to_logger_list(&app, &msg);
glib::Continue(true)
});
@ -627,13 +418,7 @@ impl GPSApp {
let app_weak = self.downgrade();
self.connect_app_menu_action("about", move |_, _| {
let app = upgrade_weak!(app_weak);
about::display_about_dialog(&app);
});
let app_weak = self.downgrade();
self.connect_button_action("button-add-plugin", move |_| {
let app = upgrade_weak!(app_weak);
GPSApp::display_plugin_list(&app);
GPSUI::about::display_about_dialog(&app);
});
let app_weak = self.downgrade();
@ -721,10 +506,11 @@ impl GPSApp {
pop_menu.set_menu_model(Some(&menu));
let app_weak = app.downgrade();
app.connect_app_menu_action("graph.add-plugin",
app.connect_app_menu_action("graph.check",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPSApp::display_plugin_list(&app);
let render_parse_launch = app.pipeline.borrow().render_gst_launch(&app.graphview.borrow());
GPSUI::message::display_message_dialog(&render_parse_launch,gtk::MessageType::Info, |_| {});
}
);
pop_menu.show();
@ -800,7 +586,7 @@ impl GPSApp {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.add-to-favorite {}", node_id);
if let Some(node) = app.graphview.borrow().node(node_id) {
app.add_to_favorite_list(node.name());
GPSUI::elements::add_to_favorite_list(&app, node.name());
};
}
);
@ -848,7 +634,7 @@ impl GPSApp {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.properties {}", node_id);
let node = app.graphview.borrow().node(node_id).unwrap();
plugindialogs::display_plugin_properties(&app, &node.name(), node_id);
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
}
);
@ -858,9 +644,9 @@ impl GPSApp {
);
// Setup the favorite list
self.setup_favorite_list();
GPSUI::elements::setup_favorite_list(self);
// Setup the favorite list
self.setup_elements_list();
GPSUI::elements::setup_elements_list(self);
let _ = self
.load_graph(

View file

@ -39,8 +39,8 @@
<menu id="graph_menu">
<section>
<item>
<attribute name="label" translatable="yes" comments="graph menu entry add plugin">_Add plugin</attribute>
<attribute name="action">app.graph.add-plugin</attribute>
<attribute name="label" translatable="yes" comments="graph menu entry check graph">_Check graph</attribute>
<attribute name="action">app.graph.check</attribute>
</item>
</section>
</menu>
@ -90,35 +90,6 @@
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkDialog" id="dialog-plugin-list">
<property name="transient-for">mainwindow</property>
<property name="default-width">320</property>
<property name="default-height">260</property>
<property name="hide-on-close">True</property>
<child>
<object class="GtkPaned">
<property name="position">200</property>s
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkTreeView" id="treeview-plugin-list"/>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkTextView" id="textview-plugin-list"/>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="dialog-plugin-properties">
<property name="transient-for">mainwindow</property>
<property name="default-width">320</property>
@ -178,14 +149,6 @@
<property name="vexpand">0</property>
<child>
<object class="GtkBox">
<child>
<object class="GtkButton" id="button-add-plugin">
<property name="hexpand">1</property>
<property name="label">gtk-add</property>
<property name="receives-default">1</property>
<property name="icon-name">list-add</property>
</object>
</child>
<child>
<object class="GtkButton" id="button-play">
<property name="hexpand">1</property>

View file

@ -16,9 +16,10 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use crate::app::GPSApp;
use crate::graphmanager::{GraphView, Node, NodeType, PortDirection};
use crate::logger;
use crate::ui::message as GPSMessage;
use crate::GPS_INFO;
use gst::glib;
@ -156,7 +157,7 @@ impl Pipeline {
err.error(),
err.debug()
);
GPSApp::show_error_dialog(
GPSMessage::display_error_dialog(
false,
format!(
"Error from {:?}: {} ({:?})",

View file

@ -18,15 +18,14 @@
// SPDX-License-Identifier: GPL-3.0-only
#[macro_use]
mod macros;
mod about;
mod app;
mod common;
mod config;
mod graphmanager;
mod ui;
#[macro_use]
mod logger;
mod gps;
mod plugindialogs;
mod settings;
use gtk::prelude::*;

View file

@ -17,24 +17,29 @@ run_command(
)
rust_sources = files(
'gps/pipeline.rs',
'gps/element.rs',
'gps/pad.rs',
'gps/mod.rs',
'graphmanager/graphview.rs',
'graphmanager/link.rs',
'graphmanager/mod.rs',
'graphmanager/node.rs',
'graphmanager/port.rs',
'about.rs',
'ui/about.rs',
'ui/elements.rs',
'ui/logger.rs',
'ui/message.rs',
'ui/mod.rs',
'ui/properties.rs',
'ui/treeview.rs',
'app.rs',
'common.rs',
'logger.rs',
'macros.rs',
'main.rs',
'gps/pipeline.rs',
'gps/element.rs',
'gps/pad.rs',
'gps/mod.rs',
'plugindialogs.rs',
'settings.rs',
'settings.rs',
)
sources = [cargo_sources, rust_sources]

View file

@ -1,172 +0,0 @@
// plugindialogs.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 crate::app::GPSApp;
use crate::gps::ElementInfo;
use crate::logger;
use gtk::glib;
use gtk::prelude::*;
use gtk::TextBuffer;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use gtk::{
Box, Button, CellRendererText, Dialog, Entry, Label, ListStore, TextView, TreeView,
TreeViewColumn,
};
fn create_and_fill_model(elements: &[ElementInfo]) -> ListStore {
// Creation of a model with two rows.
let model = ListStore::new(&[u32::static_type(), String::static_type()]);
// Filling up the tree view.
for (i, entry) in elements.iter().enumerate() {
model.insert_with_values(None, &[(0, &(i as u32 + 1)), (1, &entry.name)]);
}
model
}
fn append_column(tree: &TreeView, id: i32) {
let column = TreeViewColumn::new();
let cell = CellRendererText::new();
column.pack_start(&cell, true);
// Association of the view's column with the model's `id` column.
column.add_attribute(&cell, "text", id);
tree.append_column(&column);
}
pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
let dialog: Dialog = app
.builder
.object("dialog-plugin-list")
.expect("Couldn't get the dialog-plugin-list window");
if app.plugin_list_initialized.get().is_none() {
dialog.set_title(Some("Plugin list"));
dialog.set_default_size(640, 480);
let text_view: TextView = app
.builder
.object("textview-plugin-list")
.expect("Couldn't get textview-plugin-list window");
let text_buffer: TextBuffer = text_view.buffer();
let tree: TreeView = app
.builder
.object("treeview-plugin-list")
.expect("Couldn't get treeview-plugin-list window");
if tree.n_columns() < 2 {
append_column(&tree, 0);
append_column(&tree, 1);
}
tree.set_search_column(1);
let model = create_and_fill_model(elements);
// Setting the model into the view.
tree.set_model(Some(&model));
// The closure responds to selection changes by connection to "::cursor-changed" signal,
// that gets emitted when the cursor moves (focus changes).
tree.connect_cursor_changed(glib::clone!(@weak dialog, @weak text_buffer => move |tree_view| {
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model
.get::<String>(&iter, 1);
let description = ElementInfo::element_description(&element_name).expect("Unable to get element description from GStreamer");
text_buffer.set_text("");
text_buffer.insert_markup(&mut text_buffer.end_iter(), &description);
}
}));
let app_weak = app.downgrade();
tree.connect_row_activated(
glib::clone!(@weak dialog => move |tree_view, _tree_path, _tree_column| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model
.get::<String>(&iter, 1);
app.add_new_element(&element_name);
}
}),
);
app.plugin_list_initialized.set(true).unwrap();
}
dialog.show();
}
pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32) {
let dialog: Dialog = app
.builder
.object("dialog-plugin-properties")
.expect("Couldn't get dialog-plugin-properties");
dialog.set_title(Some(&format!("{} properties", element_name)));
dialog.set_default_size(640, 480);
dialog.set_modal(true);
let properties_box: Box = app
.builder
.object("box-plugin-properties")
.expect("Couldn't get box-plugin-properties");
let update_properties: Rc<RefCell<HashMap<String, String>>> =
Rc::new(RefCell::new(HashMap::new()));
let properties = ElementInfo::element_properties(element_name).unwrap();
for (name, value) in properties {
let entry_box = Box::new(gtk::Orientation::Horizontal, 6);
let label = Label::new(Some(&name));
label.set_hexpand(true);
label.set_halign(gtk::Align::Start);
label.set_margin_start(4);
entry_box.append(&label);
let entry: Entry = Entry::new();
entry.set_text(&value);
entry.set_hexpand(true);
entry.set_halign(gtk::Align::Start);
entry.set_widget_name(&name);
entry.connect_changed(
glib::clone!(@weak entry, @strong update_properties => move |_| {
GPS_TRACE!("property changed: {}:{}", entry.widget_name(), entry.text());
update_properties.borrow_mut().insert(entry.widget_name().to_string(), entry.text().to_string());
}),
);
entry_box.append(&entry);
properties_box.append(&entry_box);
}
let properties_apply_btn: Button = app
.builder
.object("button-apply-plugin-properties")
.expect("Couldn't get button-apply-plugin-properties");
let app_weak = app.downgrade();
properties_apply_btn.connect_clicked(
glib::clone!(@strong update_properties, @weak dialog => move |_| {
let app = upgrade_weak!(app_weak);
app.update_element_properties(node_id, &update_properties.borrow());
dialog.close();
}),
);
dialog.show();
for p in update_properties.borrow().values() {
GPS_TRACE!("updated properties {}", p);
}
}

160
src/ui/elements.rs Normal file
View file

@ -0,0 +1,160 @@
// elements.rs
//
// Copyright 2022 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 crate::app::GPSApp;
use crate::gps::ElementInfo;
use crate::logger;
use crate::settings::Settings;
use crate::ui::treeview;
use crate::GPS_DEBUG;
use gtk::prelude::*;
use gtk::{gdk::BUTTON_SECONDARY, Box, Label, ListStore, TreeView};
use gtk::{gio, glib};
pub fn reset_favorite_list(favorite_list: &TreeView) {
let model = ListStore::new(&[String::static_type()]);
favorite_list.set_model(Some(&model));
let favorites = Settings::get_favorites_list();
for favorite in favorites {
model.insert_with_values(None, &[(0, &favorite)]);
}
}
pub fn setup_favorite_list(app: &GPSApp) {
let favorite_list: TreeView = app
.builder
.object("treeview-favorites")
.expect("Couldn't get treeview-favorites");
treeview::add_column_to_treeview(app, "treeview-favorites", "Name", 0);
reset_favorite_list(&favorite_list);
let app_weak = app.downgrade();
favorite_list.connect_row_activated(move |tree_view, _tree_path, _tree_column| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model.get::<String>(&iter, 0);
GPS_DEBUG!("{} selected", element_name);
app.add_new_element(&element_name);
}
});
let gesture = gtk::GestureClick::new();
gesture.set_button(0);
let app_weak = app.downgrade();
gesture.connect_pressed(
glib::clone!(@weak favorite_list => move |gesture, _n_press, x, y| {
let app = upgrade_weak!(app_weak);
if gesture.current_button() == BUTTON_SECONDARY {
let selection = favorite_list.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model
.get::<String>(&iter, 0);
GPS_DEBUG!("Element {} selected", element_name);
let pop_menu = app.app_pop_menu_at_position(&favorite_list, x, y);
let menu: gio::MenuModel = app
.builder
.object("fav_menu")
.expect("Couldn't get fav_menu model");
pop_menu.set_menu_model(Some(&menu));
app.connect_app_menu_action("favorite.remove",
move |_,_| {
Settings::remove_favorite(&element_name);
reset_favorite_list(&favorite_list);
}
);
pop_menu.show();
}
}
}),
);
favorite_list.add_controller(&gesture);
}
pub fn add_to_favorite_list(app: &GPSApp, element_name: String) {
let favorites = Settings::get_favorites_list();
if !favorites.contains(&element_name) {
let favorite_list: TreeView = app
.builder
.object("treeview-favorites")
.expect("Couldn't get treeview-favorites");
if let Some(model) = favorite_list.model() {
let list_store = model
.dynamic_cast::<ListStore>()
.expect("Could not cast to ListStore");
list_store.insert_with_values(None, &[(0, &element_name)]);
Settings::add_favorite(&element_name);
}
}
}
fn reset_elements_list(elements_list: &TreeView) {
let model = ListStore::new(&[String::static_type()]);
elements_list.set_model(Some(&model));
let elements = ElementInfo::elements_list().expect("Unable to obtain element's list");
for element in elements {
model.insert_with_values(None, &[(0, &element.name)]);
}
}
pub fn setup_elements_list(app: &GPSApp) {
let tree: TreeView = app
.builder
.object("treeview-elements")
.expect("Couldn't get treeview-elements");
treeview::add_column_to_treeview(app, "treeview-elements", "Name", 0);
reset_elements_list(&tree);
let app_weak = app.downgrade();
tree.connect_row_activated(move |tree_view, _tree_path, _tree_column| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model.get::<String>(&iter, 0);
GPS_DEBUG!("{} selected", element_name);
app.add_new_element(&element_name);
}
});
let app_weak = app.downgrade();
tree.connect_cursor_changed(move |tree_view| {
let app = upgrade_weak!(app_weak);
let selection = tree_view.selection();
if let Some((model, iter)) = selection.selected() {
let element_name = model.get::<String>(&iter, 0);
let description = ElementInfo::element_description(&element_name)
.expect("Unable to get element description from GStreamer");
let box_property: Box = app
.builder
.object("box-property")
.expect("Couldn't get treeview-elements");
while let Some(child) = box_property.first_child() {
box_property.remove(&child);
}
let label = Label::new(Some(""));
label.set_hexpand(true);
label.set_halign(gtk::Align::Start);
label.set_margin_start(4);
label.set_markup(&description);
label.set_selectable(true);
box_property.append(&label);
}
});
}

57
src/ui/logger.rs Normal file
View file

@ -0,0 +1,57 @@
// logger.rs
//
// Copyright 2022 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 crate::app::GPSApp;
use crate::ui::treeview;
use gtk::prelude::*;
use gtk::{ListStore, TreeView};
fn reset_logger_list(logger_list: &TreeView) {
let model = ListStore::new(&[
String::static_type(),
String::static_type(),
String::static_type(),
]);
logger_list.set_model(Some(&model));
}
pub fn setup_logger_list(app: &GPSApp) {
treeview::add_column_to_treeview(app, "treeview-logger", "TIME", 0);
treeview::add_column_to_treeview(app, "treeview-logger", "LEVEL", 1);
treeview::add_column_to_treeview(app, "treeview-logger", "LOG", 2);
let logger_list: TreeView = app
.builder
.object("treeview-logger")
.expect("Couldn't get treeview-logger");
reset_logger_list(&logger_list);
}
pub fn add_to_logger_list(app: &GPSApp, log_entry: &str) {
let logger_list: TreeView = app
.builder
.object("treeview-logger")
.expect("Couldn't get treeview-logger");
if let Some(model) = logger_list.model() {
let list_store = model
.dynamic_cast::<ListStore>()
.expect("Could not cast to ListStore");
let log: Vec<&str> = log_entry.splitn(3, ' ').collect();
list_store.insert_with_values(None, &[(0, &log[0]), (1, &log[1]), (2, &log[2])]);
}
}

70
src/ui/message.rs Normal file
View file

@ -0,0 +1,70 @@
// message.rs
//
// Copyright 2022 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::gio;
use gtk::prelude::*;
use gtk::{Application, Label, Widget};
pub fn display_message_dialog<F: Fn(Application) + 'static>(
message: &str,
message_type: gtk::MessageType,
f: F,
) {
let app = gio::Application::default()
.expect("No default application")
.downcast::<gtk::Application>()
.expect("Default application has wrong type");
let dialog = gtk::MessageDialog::new(
app.active_window().as_ref(),
gtk::DialogFlags::MODAL,
message_type,
gtk::ButtonsType::Ok,
message,
);
let message_area = dialog.message_area();
let mut child = message_area.first_child();
while child.is_some() {
let widget = child.unwrap();
let label = widget
.dynamic_cast::<Label>()
.expect("unable to cast child to Label");
label.set_selectable(true);
let widget = label.dynamic_cast::<Widget>().unwrap();
child = widget.next_sibling();
}
let app_weak = app.downgrade();
dialog.connect_response(move |dialog, _| {
let app = upgrade_weak!(app_weak);
dialog.destroy();
f(app);
});
dialog.set_resizable(false);
dialog.show();
}
pub fn display_error_dialog(fatal: bool, message: &str) {
display_message_dialog(message, gtk::MessageType::Error, move |app| {
if fatal {
app.quit();
}
});
}

24
src/ui/mod.rs Normal file
View file

@ -0,0 +1,24 @@
// ui/mod.rs
//
// Copyright 2022 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
pub mod about;
pub mod elements;
pub mod logger;
pub mod message;
pub mod properties;
pub mod treeview;

88
src/ui/properties.rs Normal file
View file

@ -0,0 +1,88 @@
// properties.rs
//
// Copyright 2022 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 crate::app::GPSApp;
use crate::gps::ElementInfo;
use crate::logger;
use crate::GPS_TRACE;
use gtk::glib;
use gtk::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use gtk::{Box, Button, Dialog, Entry, Label};
pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32) {
let dialog: Dialog = app
.builder
.object("dialog-plugin-properties")
.expect("Couldn't get dialog-plugin-properties");
dialog.set_title(Some(&format!("{} properties", element_name)));
dialog.set_default_size(640, 480);
dialog.set_modal(true);
let properties_box: Box = app
.builder
.object("box-plugin-properties")
.expect("Couldn't get box-plugin-properties");
let update_properties: Rc<RefCell<HashMap<String, String>>> =
Rc::new(RefCell::new(HashMap::new()));
let properties = ElementInfo::element_properties(element_name).unwrap();
for (name, value) in properties {
let entry_box = Box::new(gtk::Orientation::Horizontal, 6);
let label = Label::new(Some(&name));
label.set_hexpand(true);
label.set_halign(gtk::Align::Start);
label.set_margin_start(4);
entry_box.append(&label);
let entry: Entry = Entry::new();
entry.set_text(&value);
entry.set_hexpand(true);
entry.set_halign(gtk::Align::Start);
entry.set_widget_name(&name);
entry.connect_changed(
glib::clone!(@weak entry, @strong update_properties => move |_| {
GPS_TRACE!("property changed: {}:{}", entry.widget_name(), entry.text());
update_properties.borrow_mut().insert(entry.widget_name().to_string(), entry.text().to_string());
}),
);
entry_box.append(&entry);
properties_box.append(&entry_box);
}
let properties_apply_btn: Button = app
.builder
.object("button-apply-plugin-properties")
.expect("Couldn't get button-apply-plugin-properties");
let app_weak = app.downgrade();
properties_apply_btn.connect_clicked(
glib::clone!(@strong update_properties, @weak dialog => move |_| {
let app = upgrade_weak!(app_weak);
app.update_element_properties(node_id, &update_properties.borrow());
dialog.close();
}),
);
dialog.show();
for p in update_properties.borrow().values() {
GPS_TRACE!("updated properties {}", p);
}
}

35
src/ui/treeview.rs Normal file
View file

@ -0,0 +1,35 @@
// treeview.rs
//
// Copyright 2022 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 crate::app::GPSApp;
use gtk::prelude::TreeViewExt;
use gtk::{CellRendererText, TreeView, TreeViewColumn};
pub fn add_column_to_treeview(app: &GPSApp, tree_name: &str, column_name: &str, column_n: i32) {
let treeview: TreeView = app
.builder
.object(tree_name)
.expect("Couldn't get tree_name");
let column = TreeViewColumn::new();
let cell = CellRendererText::new();
column.pack_start(&cell, true);
// Association of the view's column with the model's `id` column.
column.add_attribute(&cell, "text", column_n);
column.set_title(column_name);
treeview.append_column(&column);
}