From b36fd0aebed10b236adae3b968da16bbd4bc4d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 22 Nov 2021 13:08:05 +0100 Subject: [PATCH] pluginlist: can now select and double click element Treeview row actions: - A selection will display element information. - A double click add a new element in the app.graph structure. The plugin list dialog is an item from gps.ui --- TODO.md | 27 ++++---- src/app.rs | 4 +- src/gps.ui | 87 ++++++++++++++++++++++++- src/main.rs | 2 +- src/pipeline.rs | 68 ++++++++++++++++++++ src/pluginlist.rs | 136 ++++++++++++++++++++++++++++++++++++++++ src/pluginlistwindow.rs | 127 ------------------------------------- 7 files changed, 305 insertions(+), 146 deletions(-) create mode 100644 src/pluginlist.rs delete mode 100644 src/pluginlistwindow.rs diff --git a/TODO.md b/TODO.md index 21917f9..a70b13b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,15 @@ TODO: - - [X] Fix c.fill issue - - [] Create Element structure with pads and connections - - [] Get a list of GStreamer elements in dialog add plugin - - [] Draw element with its pad - - [] Be able to move the element on Screen - - [] Create connection between element - - [] Run a pipeline with GStreamer - - [] Run the pipeline with GStreamer - - [] Control the pipeline with GStreamer - - [X] Define the license - - [] Connect the logs to the window - - [] Create a window for the video output - - +- [x] Fix c.fill issue +- [x] Create Element structure with pads and connections +- [x] Get a list of GStreamer elements in dialog add plugin +- [x] Add plugin details in the element dialog +- [] Draw element with its pad +- [] Be able to move the element on Screen +- [] Create connection between element +- [] Run a pipeline with GStreamer +- [] Run the pipeline with GStreamer +- [] Control the pipeline with GStreamer +- [x] Define the license +- [] Connect the logs to the window +- [] Create a window for the video output diff --git a/src/app.rs b/src/app.rs index 662a6af..dc077f3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -29,7 +29,7 @@ use std::{error, ops}; use crate::graph::{Element, Graph}; use crate::pipeline::Pipeline; -use crate::pluginlistwindow; +use crate::pluginlist; #[derive(Debug)] pub struct GPSAppInner { @@ -186,7 +186,7 @@ impl GPSApp { 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); + pluginlist::display_plugin_list(&app, &elements); })); // Create a dialog to open a file let open_button: Button = self diff --git a/src/gps.ui b/src/gps.ui index 8c8647f..571a838 100644 --- a/src/gps.ui +++ b/src/gps.ui @@ -67,6 +67,90 @@ + + False + 320 + 260 + dialog + + + False + vertical + 2 + + + False + end + + + + + + + + + False + False + 0 + + + + + True + False + True + + + True + True + True + in + + + True + True + + + + + + + + False + True + 0 + + + + + True + True + True + in + + + True + True + False + + + + + False + True + 1 + + + + + False + True + 1 + + + + + False 6 @@ -374,7 +458,7 @@ - + True True True @@ -423,7 +507,6 @@ True False True - True True diff --git a/src/main.rs b/src/main.rs index ec989e6..c6301bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ mod macros; mod app; mod graph; mod pipeline; -mod pluginlistwindow; +mod pluginlist; use gtk::prelude::*; diff --git a/src/pipeline.rs b/src/pipeline.rs index c23bfcd..9e42d82 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -73,6 +73,74 @@ impl Pipeline { } } } + elements.sort(); Ok(elements) } + pub fn element_description( + element_name: &str, + ) -> anyhow::Result> { + let mut desc = String::from(""); + let registry = gst::Registry::get(); + let feature = gst::Registry::find_feature( + ®istry, + element_name, + gst::ElementFactory::static_type(), + ) + .expect("Unable to find the element name"); + + if let Ok(factory) = feature.downcast::() { + desc.push_str("Factory details:\n"); + desc.push_str("Name:"); + desc.push_str(&factory.get_name()); + desc.push('\n'); + + let element_keys = factory.get_metadata_keys(); + for key in element_keys { + let val = factory.get_metadata(&key); + if let Some(val) = val { + desc.push_str(""); + desc.push_str(&key); + desc.push_str(":"); + desc.push_str(>k::glib::markup_escape_text(&val).to_string()); + desc.push('\n'); + } + } + let feature = factory.upcast::(); + let plugin = gst::PluginFeature::get_plugin(&feature); + if let Some(plugin) = plugin { + desc.push('\n'); + desc.push_str("Plugin details:"); + desc.push('\n'); + desc.push_str("Name:"); + desc.push_str(""); + desc.push_str(gst::Plugin::get_plugin_name(&plugin).as_str()); + desc.push('\n'); + desc.push_str("Description:"); + desc.push_str(""); + desc.push_str( + >k::glib::markup_escape_text(&plugin.get_description()).to_string(), + ); + desc.push('\n'); + desc.push_str("Filename:"); + desc.push_str(""); + desc.push_str( + >k::glib::markup_escape_text( + &plugin + .get_filename() + .unwrap() + .as_path() + .display() + .to_string(), + ) + .to_string(), + ); + desc.push('\n'); + desc.push_str("Version:"); + desc.push_str(""); + desc.push_str(>k::glib::markup_escape_text(&plugin.get_version()).to_string()); + desc.push('\n'); + } + } + Ok(desc) + } } diff --git a/src/pluginlist.rs b/src/pluginlist.rs new file mode 100644 index 0000000..2e86dd4 --- /dev/null +++ b/src/pluginlist.rs @@ -0,0 +1,136 @@ +// pluginlist.rs +// +// Copyright 2021 Stéphane Cerveau +// +// 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 . +// +// SPDX-License-Identifier: GPL-3.0-only +use crate::app::GPSApp; +use crate::graph::Element; +use crate::pipeline::ElementInfo; +use crate::pipeline::Pipeline; +use gtk::TextBuffer; +use gtk::{ + glib::{self, clone}, + prelude::*, +}; + +use gtk::{ + CellRendererText, Dialog, ListStore, TextView, TreeView, TreeViewColumn, WindowPosition, +}; + +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.as_ref().unwrap())], + ); + } + 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 window"); + + dialog.set_title("Plugin list"); + dialog.set_position(WindowPosition::Center); + dialog.set_default_size(640, 480); + + let tree: TreeView = app + .builder + .object("treeview-plugin-list") + .expect("Couldn't get window"); + + let text_view: TextView = app + .builder + .object("textview-plugin-list") + .expect("Couldn't get window"); + let text_buffer: TextBuffer = text_view + .buffer() + .expect("Couldn't get buffer from text_view"); + if tree.n_columns() < 2 { + append_column(&tree, 0); + append_column(&tree, 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(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 + .value(&iter, 1) + .get::() + .expect("Treeview selection, column 1"); + let description = Pipeline::element_description(&element_name).expect("Unable to get element list 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( + 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() { + // Now getting back the values from the row corresponding to the + // iterator `iter`. + // + let element = Element { + name: model + .value(&iter, 1) + .get::() + .expect("Treeview selection, column 1"), + position: (100.0,100.0), + size: (100.0,100.0), + }; + + let element_name = model + .value(&iter, 1) + .get::() + .expect("Treeview selection, column 1"); + app.add_new_element(element); + + println!("{}", element_name); + } + }), + ); + + dialog.connect_delete_event(|dialog, _| { + dialog.hide(); + gtk::Inhibit(true) + }); + dialog.show_all(); +} diff --git a/src/pluginlistwindow.rs b/src/pluginlistwindow.rs deleted file mode 100644 index 1178b7a..0000000 --- a/src/pluginlistwindow.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::app::GPSApp; -use crate::graph::Element; -use crate::pipeline::ElementInfo; -use gtk::{ - glib::{self, clone}, - prelude::*, - ResponseType, -}; - -use gtk::{ - CellRendererText, Label, ListStore, Orientation, TreeView, TreeViewColumn, WindowPosition, -}; - -fn create_and_fill_model(elements: &Vec) -> 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.as_ref().unwrap())], - ); - } - 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); -} - -fn create_and_setup_view() -> TreeView { - // Creating the tree view. - let tree = TreeView::new(); - - tree.set_headers_visible(false); - // Creating the two columns inside the view. - append_column(&tree, 0); - append_column(&tree, 1); - tree -} - -pub fn build_plugin_list(app: &GPSApp, elements: &Vec) { - let dialog = gtk::Dialog::with_buttons( - Some("Edit Item"), - Some(&app.window), - gtk::DialogFlags::MODAL, - &[("Close", ResponseType::Close)], - ); - dialog.set_title("Plugin list"); - dialog.set_position(WindowPosition::Center); - dialog.set_default_size(640, 480); - - // Creating a vertical layout to place both tree view and label in the window. - let vertical_layout = gtk::Box::new(Orientation::Vertical, 0); - - // Creation of the label. - let label = Label::new(Some("")); - - let tree = create_and_setup_view(); - - let model = create_and_fill_model(elements); - // Setting the model into the view. - tree.set_model(Some(&model)); - - // Adding the view to the layout. - vertical_layout.add(&tree); - // Same goes for the label. - vertical_layout.add(&label); - - // The closure responds to selection changes by connection to "::cursor-changed" signal, - // that gets emitted when the cursor moves (focus changes). - let app_weak = app.downgrade(); - tree.connect_cursor_changed(clone!(@weak dialog => move |tree_view| { - let app = upgrade_weak!(app_weak); - let selection = tree_view.selection(); - if let Some((model, iter)) = selection.selected() { - // Now getting back the values from the row corresponding to the - // iterator `iter`. - // - // The `get_value` method do the conversion between the gtk type and Rust. - label.set_text(&format!( - "Hello '{}' from row {}", - model - .value(&iter, 1) - .get::() - .expect("Treeview selection, column 1"), - model - .value(&iter, 0) - .get::() - .expect("Treeview selection, column 0"), - )); - let element = Element { - name: model - .value(&iter, 1) - .get::() - .expect("Treeview selection, column 1"), - position: (100.0,100.0), - size: (100.0,100.0), - }; - - let element_name = model - .value(&iter, 1) - .get::() - .expect("Treeview selection, column 1"); - app.add_new_element(element); - - //dialog.close(); - println!("{}", element_name); - } - })); - - // Adding the layout to the window. - let content_area = dialog.content_area(); - let scrolled_window = gtk::ScrolledWindow::new(gtk::NONE_ADJUSTMENT, gtk::NONE_ADJUSTMENT); - scrolled_window.add(&vertical_layout); - content_area.add(&scrolled_window); - - dialog.connect_response(|dialog, _| dialog.close()); - dialog.show_all(); -}