use tracing::info;
use super::Manipulate;
use crate::{
annotations_accessor_mut,
events::Events,
file_util::PathPair,
history::{History, Record},
make_tool_transform,
parameters::{ParamMap, ParamVal},
result::trace_ok_err,
tools_data::{AttributesToolData, attributes_data::set_attrmap_val},
tools_data_accessors,
world::World,
world_annotations_accessor,
};
use std::mem;
const MISSING_DATA_MSG: &str = "Missing data for Attributes";
pub const ACTOR_NAME: &str = "Attributes";
annotations_accessor_mut!(
ACTOR_NAME,
attributes_mut,
"Attribute didn't work",
ParamMap
);
world_annotations_accessor!(ACTOR_NAME, attributes, "Attribute didn't work", ParamMap);
tools_data_accessors!(
ACTOR_NAME,
MISSING_DATA_MSG,
attributes_data,
AttributesToolData,
attributes,
attributes_mut
);
fn propagate_annos(
mut annos: ParamMap,
attr_names: &[String],
to_propagate: &[(usize, ParamVal)],
) -> ParamMap {
for (attr_idx, val) in to_propagate {
if let Some(attr_val) = attr_names
.get(*attr_idx)
.and_then(|attr_name| annos.get_mut(attr_name))
{
*attr_val = val.clone();
}
}
annos
}
fn get_buffers(world: &World) -> Vec<String> {
let annos = get_annos(world);
let data = get_specific(world);
if let (Some(data), Some(annos)) = (data, annos) {
data.attr_names()
.iter()
.map(|attr_name| {
if let Some(attrval) = annos.get(attr_name) {
attrval.to_string()
} else {
"".to_string()
}
})
.collect()
} else {
vec![]
}
}
fn propagate_buffer(
mut attribute_buffer: Vec<String>,
to_propagate: &[(usize, ParamVal)],
) -> Vec<String> {
for (attr_idx, val) in to_propagate {
if let Some(ab) = attribute_buffer.get_mut(*attr_idx) {
*ab = val.to_string();
}
}
attribute_buffer
}
fn apply_menu_update(world: &mut World) -> bool {
let is_update_triggered = get_specific(world).map(|d| d.options.is_update_triggered);
if is_update_triggered == Some(true) {
info!("update attr");
let current_from_menu_clone = get_specific(world).and_then(|d| d.current_attr_map.clone());
if let (Some(mut cfm), Some(anno)) = (current_from_menu_clone, get_annos_mut(world)) {
*anno = mem::take(&mut cfm);
}
if let Some(update_current_attr_map) =
get_specific_mut(world).map(|d| &mut d.options.is_update_triggered)
{
*update_current_attr_map = false;
}
true
} else {
false
}
}
fn file_change(mut world: World) -> World {
use_currentimageshape_for_annos(&mut world);
let attr_buffers = get_buffers(&world);
let annos = get_annos_mut(&mut world).map(mem::take);
let data = get_specific_mut(&mut world);
if let (Some(data), Some(mut annos)) = (data, annos) {
for (attr_name, attr_val) in data.attr_names().iter().zip(data.attr_vals().iter()) {
if !annos.contains(attr_name) {
set_attrmap_val(&mut annos, attr_name, attr_val.clone().reset());
}
}
for (attr_name, attr_val) in annos.iter() {
if !data.attr_names().contains(attr_name) {
tracing::warn!(
"Attribute {attr_name} exists in the data but not in the tool data, adding it"
);
data.push(attr_name.clone(), attr_val.clone().reset());
}
}
let attr_buffers = propagate_buffer(attr_buffers, &data.to_propagate_attr_val);
for (i, buffer) in attr_buffers.into_iter().enumerate() {
if let Some(attr_buffer) = data.attr_value_buffer_mut(i) {
*attr_buffer = buffer;
}
}
annos = propagate_annos(annos, data.attr_names(), &data.to_propagate_attr_val);
if let Some(annos_) = get_annos_mut(&mut world) {
*annos_ = annos;
}
}
let current = get_annos(&world).cloned();
if let Some(data) = get_specific_mut(&mut world) {
data.current_attr_map = current;
}
world
}
fn add_attribute(
mut world: World,
mut history: History,
suppress_exists_err: bool,
) -> (World, History) {
let attr_map_tmp = get_annos_mut(&mut world).map(mem::take);
let data = get_specific_mut(&mut world);
if let (Some(mut attr_map_tmp), Some(data)) = (attr_map_tmp, data) {
let new_attr_name = data.new_attr_name.clone();
if data.attr_names().contains(&new_attr_name) && !suppress_exists_err {
tracing::error!("New attribute {new_attr_name} could not be created, already exists");
} else {
let new_attr_val = data.new_attr_val.clone();
for (_, (val_map, _)) in data.anno_iter_mut() {
set_attrmap_val(val_map, &new_attr_name, new_attr_val.clone());
}
set_attrmap_val(&mut attr_map_tmp, &new_attr_name, new_attr_val.clone());
if let Some(a) = get_annos_mut(&mut world) {
a.clone_from(&attr_map_tmp);
}
if let Some(data) = get_specific_mut(&mut world) {
data.current_attr_map = Some(attr_map_tmp);
data.push(new_attr_name, new_attr_val);
history.push(Record::new(world.clone(), ACTOR_NAME));
}
}
}
if let Some(data) = get_specific_mut(&mut world) {
data.options.is_addition_triggered = false;
data.new_attr_name = String::new();
data.new_attr_val = ParamVal::default();
}
(world, history)
}
fn check_remove(mut world: World, mut history: History) -> (World, History) {
if let Some(removal_idx) = get_specific(&world).map(|d| d.options.removal_idx) {
let data = get_specific_mut(&mut world);
if let (Some(data), Some(removal_idx)) = (data, removal_idx) {
data.remove_attr(removal_idx);
history.push(Record::new(world.clone(), ACTOR_NAME));
}
if let Some(removal_idx) = get_specific_mut(&mut world).map(|d| &mut d.options.removal_idx)
{
*removal_idx = None;
}
}
(world, history)
}
#[derive(Clone, Copy, Debug)]
pub struct Attributes;
impl Manipulate for Attributes {
fn new() -> Self
where
Self: Sized,
{
Self
}
fn on_activate(&mut self, mut world: World) -> World {
let data = get_data_mut(&mut world);
if let Some(data) = trace_ok_err(data) {
data.menu_active = true;
}
file_change(world)
}
fn on_deactivate(&mut self, mut world: World) -> World {
let data = get_data_mut(&mut world);
if let Some(data) = trace_ok_err(data) {
data.menu_active = false;
}
world
}
fn on_filechange(&mut self, world: World, history: History) -> (World, History) {
(file_change(world), history)
}
fn before_file_change(&mut self, mut world: World) -> World {
apply_menu_update(&mut world);
world
}
fn events_tf(
&mut self,
mut world: World,
mut history: History,
_event: &Events,
) -> (World, History) {
let is_addition_triggered = get_specific(&world).map(|d| d.options.is_addition_triggered);
if is_addition_triggered == Some(true) {
(world, history) = add_attribute(world, history, false);
}
let attr_data = get_specific_mut(&mut world);
if let Some(attr_data) = attr_data
&& let Some(rename_src_idx) = attr_data.options.rename_src_idx
{
let from_name = attr_data.attr_names().get(rename_src_idx).cloned();
let to_name = &attr_data.new_attr_name.clone();
if let Some(from_name) = from_name {
tracing::info!("Rename attribute {from_name} to {to_name}");
attr_data.rename(&from_name, to_name);
attr_data.options.rename_src_idx = None;
} else {
tracing::error!("could not rename attribute {from_name:?} to {to_name}");
}
}
(world, history) = check_remove(world, history);
let is_export_triggered =
get_specific(&world).map(|d| d.options.import_export_trigger.export_triggered());
if is_export_triggered == Some(true) {
let ssh_cfg = world.data.meta_data.ssh_cfg.clone();
let attr_data = get_specific(&world);
let export_only_opened_folder =
attr_data.map(|d| d.options.export_only_opened_folder) == Some(true);
let key_filter = if export_only_opened_folder {
world
.data
.meta_data
.opened_folder
.as_ref()
.map(PathPair::path_relative)
} else {
None
};
let annos_str = get_specific(&world)
.and_then(|d| trace_ok_err(d.serialize_annotations(key_filter)));
if let (Some(annos_str), Some(data)) = (annos_str, get_specific(&world))
&& trace_ok_err(data.export_path.conn.write(
&annos_str,
&data.export_path.path,
ssh_cfg.as_ref(),
))
.is_some()
{
info!("exported annotations to {:?}", data.export_path.path);
}
if let Some(export_triggered) =
get_specific_mut(&mut world).map(|d| &mut d.options.import_export_trigger)
{
export_triggered.untrigger_export();
}
}
let is_import_triggered =
get_specific(&world).map(|d| d.options.import_export_trigger.import_triggered());
if is_import_triggered == Some(true) {
tracing::info!("import attr tiggered");
let ssh_cfg = world.data.meta_data.ssh_cfg.clone();
let cur_prj = world.data.meta_data.prj_path().map(|p| p.to_path_buf());
let attr_data = get_specific_mut(&mut world);
let imported_map = attr_data.and_then(|data| {
let in_path = &data.export_path.path;
tracing::info!("importing attributes from {in_path:?}");
let json_str = trace_ok_err(data.export_path.conn.read(in_path, ssh_cfg.as_ref()));
if let Some(s) = json_str {
trace_ok_err(AttributesToolData::deserialize_annotations(
&s,
cur_prj.as_deref(),
))
} else {
None
}
});
if let Some(imported_map) = &imported_map {
for (_, (attr_map, _)) in imported_map.iter() {
for (attr_name, attr_val) in attr_map.iter() {
let data = get_specific_mut(&mut world);
if let Some(d) = data {
d.new_attr_name = attr_name.clone();
d.new_attr_val = attr_val.clone().reset();
}
tracing::debug!("inserting attr {attr_name} with value {attr_val}");
(world, history) = add_attribute(world, history, true);
}
}
}
if let Some(imported_map) = imported_map {
let data = get_specific_mut(&mut world);
if let Some(d) = data {
d.merge_map(imported_map);
}
}
let annos = get_annos(&world).cloned();
let attr_buffer = get_buffers(&world);
if let (Some(data), Some(annos)) = (get_specific_mut(&mut world), annos) {
data.current_attr_map = Some(annos);
data.set_new_attr_value_buffer(attr_buffer);
}
}
if let Some(import_trigger) =
get_specific_mut(&mut world).map(|d| &mut d.options.import_export_trigger)
{
import_trigger.untrigger_import();
}
make_tool_transform!(self, world, history, event, [])
}
}
#[cfg(test)]
use {
crate::tracing_setup::init_tracing_for_tests,
crate::types::{ThumbIms, ViewImage},
image::DynamicImage,
std::collections::HashMap,
std::fs,
std::path::Path,
};
#[cfg(test)]
pub(super) fn test_data() -> (World, History) {
use std::path::Path;
use crate::ToolsDataMap;
let im_test = DynamicImage::ImageRgb8(ViewImage::new(64, 64));
let mut world = World::from_real_im(
im_test,
ThumbIms::default(),
ToolsDataMap::new(),
None,
Some("superimage.png".to_string()),
Path::new("superimage.png"),
Some(0),
);
world.data.meta_data.flags.is_loading_screen_active = Some(false);
let history = History::default();
(world, history)
}
#[test]
fn test_import_export() {
init_tracing_for_tests();
fn test(testpath: &Path) {
let (mut world, history) = test_data();
let data = get_specific_mut(&mut world).unwrap();
let json_str = fs::read_to_string(testpath).unwrap();
let reference_data = AttributesToolData::deserialize_annotations(&json_str, None).unwrap();
tracing::debug!("reference_data: {:?}", reference_data);
data.export_path.path = testpath.to_path_buf();
data.options.import_export_trigger.trigger_import();
let events = Events::default();
let (world, _) = Attributes {}.events_tf(world, history, &events);
let annos = world.data.tools_data_map[ACTOR_NAME]
.specifics
.attributes()
.unwrap()
.anno_iter()
.collect::<HashMap<_, _>>();
tracing::debug!("annos: {:?}", annos);
for k in reference_data.keys() {
tracing::debug!("k: {:?}", k);
let (annos, _) = annos.get(k).unwrap();
let (ref_annos, _) = &reference_data[k];
assert_eq!(annos, ref_annos);
}
let current = get_annos(&world).unwrap();
for v in current.values() {
assert!(v.is_default());
}
}
let testpath = Path::new("resources/test_data/attr_import.json");
test(testpath);
let testpath = Path::new("resources/test_data/attr_import_untagged.json");
test(testpath);
}
#[test]
fn test_add() {
init_tracing_for_tests();
let mut attr_tool = Attributes::new();
let events = Events::default();
let (mut world, history) = test_data();
let attr_data = get_specific_mut(&mut world).unwrap();
attr_data.options.is_addition_triggered = true;
attr_data.new_attr_name = "a attr".to_string();
attr_data.new_attr_val = ParamVal::Int(Some(1));
let (mut world, history) = attr_tool.events_tf(world, history, &events);
let attr_data = get_specific_mut(&mut world).unwrap();
attr_data.options.is_addition_triggered = true;
attr_data.new_attr_name = "c attr".to_string();
attr_data.new_attr_val = ParamVal::Int(Some(2));
let (mut world, history) = attr_tool.events_tf(world, history, &events);
let attr_data = get_specific_mut(&mut world).unwrap();
attr_data.options.is_addition_triggered = true;
attr_data.new_attr_name = "b attr".to_string();
attr_data.new_attr_val = ParamVal::Int(Some(3));
let (world, _) = attr_tool.events_tf(world, history, &events);
let data = get_specific(&world).unwrap();
let cam = data.current_attr_map.as_ref().unwrap();
let c_attr_val = cam.get("c attr").unwrap();
assert_eq!(c_attr_val, &ParamVal::Int(Some(2)));
let b_attr_val = cam.get("b attr").unwrap();
assert_eq!(b_attr_val, &ParamVal::Int(Some(3)));
let a_attr_val = cam.get("a attr").unwrap();
assert_eq!(a_attr_val, &ParamVal::Int(Some(1)));
assert_eq!(data.attr_names(), &["a attr", "b attr", "c attr"]);
assert_eq!(
data.attr_vals(),
&[
ParamVal::Int(Some(1)),
ParamVal::Int(Some(3)),
ParamVal::Int(Some(2)),
]
);
}
#[test]
fn test_rm_add() {
init_tracing_for_tests();
let (mut world, history) = test_data();
let attr_data = get_specific_mut(&mut world).unwrap();
attr_data.options.is_addition_triggered = true;
attr_data.new_attr_name = "test_attr".to_string();
attr_data.new_attr_val = ParamVal::Str("123".into());
let (mut world, history) = add_attribute(world, history, false);
let attr_data = get_specific_mut(&mut world).unwrap();
attr_data.options.removal_idx = Some(0);
let (mut world, _) = check_remove(world, history);
let attr_data = get_specific_mut(&mut world).unwrap();
assert!(!attr_data.options.is_addition_triggered);
assert!(attr_data.options.removal_idx.is_none());
assert_eq!(
attr_data.current_attr_map.as_ref().map(|cam| cam.len()),
Some(0)
);
}