use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread::spawn;
use cursive::view::{Nameable, Resizable, Scrollable};
use cursive::views::Panel;
use cursive::views::{LinearLayout, NamedView, ResizedView, ScrollView};
use cursive::Cursive;
use cursive_table_view::TableView;
use cursive_tree_view::Placement;
use cursive_tree_view::TreeView;
use libntdsextract2::{CDatabase, EntryId, ObjectTreeEntry};
mod attribute_column;
mod attribute_line;
mod tree_entry;
use attribute_column::*;
use attribute_line::*;
use tree_entry::*;
const TREE: &str = "tree_view";
const ATTRIBUTES: &str = "attributes_view";
const LEFT_PANEL: &str = "left_panel";
const RIGHT_PANEL: &str = "right_panel";
const LAYOUT: &str = "layout";
pub fn run_tui<'db, 'info>(database: CDatabase<'db, 'info>) -> anyhow::Result<()> {
let mut tree_view = TreeView::new();
insert_item(&mut tree_view, Arc::clone(database.object_tree().root()), 0);
let (tx_entry_id, rx_entry_id) = channel();
let (tx_attribs, rx_attribs) = channel();
let ui_thread = spawn(move || {
ui_thread_fn(
tree_view,
Arc::new(Mutex::new(tx_entry_id)),
Arc::new(Mutex::new(rx_attribs)),
)
});
loop {
match rx_entry_id.recv() {
Ok(entry_id) => {
let attributes = if let Ok(Some(entry)) = database.entry(entry_id) {
let attributes: Vec<_> = entry
.all_attributes()
.iter()
.map(|(i, a)| AttributeLine::from(i, a))
.collect();
attributes
} else {
vec![]
};
if tx_attribs.send(attributes).is_err() {
drop(rx_entry_id);
break;
}
}
Err(_) => {
drop(tx_attribs);
break;
}
}
}
let _ = ui_thread.join();
Ok(())
}
fn insert_item(tree_view: &mut TreeView<TreeEntry>, item: Arc<ObjectTreeEntry>, row: usize) {
if item.children().lock().unwrap().is_empty() {
tree_view.insert_item(TreeEntry::from(item), Placement::LastChild, row);
} else {
let row = tree_view
.insert_container_item(
TreeEntry::from(Arc::clone(&item)),
Placement::LastChild,
row,
)
.unwrap_or(0);
tree_view.set_collapsed(row, false);
for child in item.children().lock().unwrap().iter() {
insert_item(tree_view, Arc::clone(child), row);
}
tree_view.set_collapsed(row, true);
}
}
fn ui_thread_fn(
mut tree_view: TreeView<TreeEntry>,
tx_entry_id: Arc<Mutex<Sender<EntryId>>>,
rx_attribs: Arc<Mutex<Receiver<Vec<AttributeLine>>>>,
) {
let mut siv = cursive::default();
let attributes: TableView<AttributeLine, AttributeColumn> = TableView::new()
.column(AttributeColumn::Name, "Name", |c| {
c.align(cursive::align::HAlign::Left).width(33)
})
.column(AttributeColumn::Type, "Type", |c| {
c.align(cursive::align::HAlign::Left).width(12)
})
.column(AttributeColumn::Value, "Value", |c| {
c.align(cursive::align::HAlign::Left)
})
.default_column(AttributeColumn::Name);
siv.add_global_callback('q', |s| s.quit());
siv.add_global_callback('+', enlarge_tree);
siv.add_global_callback('-', shrink_tree);
let tx_entry_id_on_select = Arc::clone(&tx_entry_id);
let rx_attribs_on_select = Arc::clone(&rx_attribs);
tree_view.set_on_select(move |siv, row| {
let tx_entry_id = Arc::clone(&tx_entry_id_on_select);
let rx_attribs = Arc::clone(&rx_attribs_on_select);
on_select(siv, row, tx_entry_id, rx_attribs)
});
let tree_view_width = 60;
let ntds_view = LinearLayout::horizontal()
.child(
Panel::new(tree_view.with_name(TREE).scrollable())
.fixed_width(tree_view_width)
.with_name(LEFT_PANEL),
)
.child(Panel::new(attributes.with_name(ATTRIBUTES).full_screen()).with_name(RIGHT_PANEL))
.with_name(LAYOUT)
.full_screen();
siv.set_user_data(tree_view_width);
siv.add_layer(ntds_view);
siv.run();
if let Ok(tx) = tx_entry_id.lock() {
drop(tx)
}
if let Ok(rx) = rx_attribs.lock() {
drop(rx)
}
}
fn on_select(
siv: &mut Cursive,
row: usize,
tx_entry_id: Arc<Mutex<Sender<EntryId>>>,
rx_attribs: Arc<Mutex<Receiver<Vec<AttributeLine>>>>,
) {
let tree_view = siv.find_name::<TreeView<TreeEntry>>(TREE).unwrap();
if let Some(entry) = tree_view.borrow_item(row) {
match tx_entry_id.lock() {
Ok(tx) => {
if tx
.send(EntryId::Id(*entry.entry().record_ptr().ds_record_id()))
.is_err()
{
log::error!("unable to send record ID");
siv.quit();
return;
}
}
_ => {
log::error!("unable to acquire sender lock");
siv.quit();
return;
}
}
let attribs: Vec<AttributeLine> = match rx_attribs.lock() {
Ok(rx) => match rx.recv() {
Ok(attribs) => attribs,
Err(_) => {
siv.quit();
return;
}
},
_ => {
log::error!("unable to acquire receiver lock");
siv.quit();
return;
}
};
let mut attributes = siv
.find_name::<TableView<AttributeLine, AttributeColumn>>(ATTRIBUTES)
.unwrap();
attributes.set_items(attribs);
attributes.set_selected_row(0);
}
}
fn enlarge_tree(siv: &mut Cursive) {
let mut left_panel = siv
.find_name::<ResizedView<Panel<ScrollView<NamedView<TreeView<TreeEntry>>>>>>(LEFT_PANEL)
.unwrap();
siv.with_user_data(move |tree_view_width| {
*tree_view_width += 1;
left_panel.set_width(cursive::view::SizeConstraint::Fixed(*tree_view_width));
});
}
fn shrink_tree(siv: &mut Cursive) {
let mut left_panel = siv
.find_name::<ResizedView<Panel<ScrollView<NamedView<TreeView<TreeEntry>>>>>>(LEFT_PANEL)
.unwrap();
siv.with_user_data(move |tree_view_width| {
*tree_view_width -= 1;
left_panel.set_width(cursive::view::SizeConstraint::Fixed(*tree_view_width));
});
}