mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-15 21:31:03 +00:00
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:
parent
71e68b846f
commit
a2503ce86b
13 changed files with 476 additions and 460 deletions
262
src/app.rs
262
src/app.rs
|
@ -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(
|
||||
|
|
41
src/gps.ui
41
src/gps.ui
|
@ -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>
|
||||
|
|
|
@ -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 {:?}: {} ({:?})",
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
160
src/ui/elements.rs
Normal 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
57
src/ui/logger.rs
Normal 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
70
src/ui/message.rs
Normal 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
24
src/ui/mod.rs
Normal 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
88
src/ui/properties.rs
Normal 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
35
src/ui/treeview.rs
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue