Skip to main content

rvlib/tools/
attributes.rs

1use tracing::info;
2
3use super::Manipulate;
4use crate::{
5    annotations_accessor_mut,
6    events::Events,
7    file_util::PathPair,
8    history::{History, Record},
9    make_tool_transform,
10    result::trace_ok_err,
11    tools_data::{
12        AttributesToolData,
13        attributes_data::set_attrmap_val,
14        parameters::{ParamMap, ParamVal},
15    },
16    tools_data_accessors,
17    world::World,
18    world_annotations_accessor,
19};
20use std::mem;
21const MISSING_DATA_MSG: &str = "Missing data for Attributes";
22pub const ACTOR_NAME: &str = "Attributes";
23annotations_accessor_mut!(
24    ACTOR_NAME,
25    attributes_mut,
26    "Attribute didn't work",
27    ParamMap
28);
29world_annotations_accessor!(ACTOR_NAME, attributes, "Attribute didn't work", ParamMap);
30tools_data_accessors!(
31    ACTOR_NAME,
32    MISSING_DATA_MSG,
33    attributes_data,
34    AttributesToolData,
35    attributes,
36    attributes_mut
37);
38
39fn propagate_annos(
40    mut annos: ParamMap,
41    attr_names: &[String],
42    to_propagate: &[(usize, ParamVal)],
43) -> ParamMap {
44    for (attr_idx, val) in to_propagate {
45        if let Some(attr_val) = annos.get_mut(&attr_names[*attr_idx]) {
46            *attr_val = val.clone();
47        }
48    }
49    annos
50}
51
52fn get_buffers(world: &World) -> Vec<String> {
53    let annos = get_annos(world);
54    let data = get_specific(world);
55    if let (Some(data), Some(annos)) = (data, annos) {
56        data.attr_names()
57            .iter()
58            .map(|attr_name| {
59                if let Some(attrval) = annos.get(attr_name) {
60                    attrval.to_string()
61                } else {
62                    "".to_string()
63                }
64            })
65            .collect()
66    } else {
67        vec![]
68    }
69}
70fn propagate_buffer(
71    mut attribute_buffer: Vec<String>,
72    to_propagate: &[(usize, ParamVal)],
73) -> Vec<String> {
74    for (attr_idx, val) in to_propagate {
75        attribute_buffer[*attr_idx] = val.to_string();
76    }
77    attribute_buffer
78}
79fn file_change(mut world: World) -> World {
80    use_currentimageshape_for_annos(&mut world);
81    let attr_buffers = get_buffers(&world);
82    let annos = get_annos_mut(&mut world).map(mem::take);
83    let data = get_specific_mut(&mut world);
84
85    if let (Some(data), Some(mut annos)) = (data, annos) {
86        // add all attributes to a new file
87        for (attr_name, attr_val) in data.attr_names().iter().zip(data.attr_vals().iter()) {
88            if !annos.contains(attr_name) {
89                set_attrmap_val(&mut annos, attr_name, attr_val.clone().reset());
90            }
91        }
92
93        // the other way around, check if attributes exist in the data but not as part of the tool
94        // smells like a corrupt project file if this happens
95        for (attr_name, attr_val) in annos.iter() {
96            if !data.attr_names().contains(attr_name) {
97                tracing::warn!(
98                    "Attribute {attr_name} exists in the data but not in the tool data, adding it"
99                );
100                data.push(attr_name.clone(), attr_val.clone().reset());
101            }
102        }
103
104        // put string representations of the attribute values into the buffer
105        let attr_buffers = propagate_buffer(attr_buffers, &data.to_propagate_attr_val);
106        for (i, buffer) in attr_buffers.into_iter().enumerate() {
107            *data.attr_value_buffer_mut(i) = buffer;
108        }
109
110        annos = propagate_annos(annos, data.attr_names(), &data.to_propagate_attr_val);
111
112        if let Some(annos_) = get_annos_mut(&mut world) {
113            *annos_ = annos;
114        }
115    }
116    let current = get_annos(&world).cloned();
117    if let Some(data) = get_specific_mut(&mut world) {
118        data.current_attr_map = current;
119    }
120    world
121}
122fn add_attribute(
123    mut world: World,
124    mut history: History,
125    suppress_exists_err: bool,
126) -> (World, History) {
127    let attr_map_tmp = get_annos_mut(&mut world).map(mem::take);
128    let data = get_specific_mut(&mut world);
129
130    if let (Some(mut attr_map_tmp), Some(data)) = (attr_map_tmp, data) {
131        let new_attr_name = data.new_attr_name.clone();
132        if data.attr_names().contains(&new_attr_name) && !suppress_exists_err {
133            tracing::error!("New attribute {new_attr_name} could not be created, already exists");
134        } else {
135            let new_attr_val = data.new_attr_val.clone();
136            for (_, (val_map, _)) in data.anno_iter_mut() {
137                set_attrmap_val(val_map, &new_attr_name, new_attr_val.clone());
138            }
139            set_attrmap_val(&mut attr_map_tmp, &new_attr_name, new_attr_val.clone());
140            if let Some(a) = get_annos_mut(&mut world) {
141                a.clone_from(&attr_map_tmp);
142            }
143            if let Some(data) = get_specific_mut(&mut world) {
144                data.current_attr_map = Some(attr_map_tmp);
145                data.push(new_attr_name, new_attr_val);
146                history.push(Record::new(world.clone(), ACTOR_NAME));
147            }
148        }
149    }
150    if let Some(data) = get_specific_mut(&mut world) {
151        data.options.is_addition_triggered = false;
152        data.new_attr_name = String::new();
153        data.new_attr_val = ParamVal::default();
154    }
155    (world, history)
156}
157
158fn check_remove(mut world: World, mut history: History) -> (World, History) {
159    if let Some(removal_idx) = get_specific(&world).map(|d| d.options.removal_idx) {
160        let data = get_specific_mut(&mut world);
161        if let (Some(data), Some(removal_idx)) = (data, removal_idx) {
162            data.remove_attr(removal_idx);
163            history.push(Record::new(world.clone(), ACTOR_NAME));
164        }
165        if let Some(removal_idx) = get_specific_mut(&mut world).map(|d| &mut d.options.removal_idx)
166        {
167            *removal_idx = None;
168        }
169    }
170    (world, history)
171}
172
173#[derive(Clone, Copy, Debug)]
174pub struct Attributes;
175
176impl Manipulate for Attributes {
177    fn new() -> Self
178    where
179        Self: Sized,
180    {
181        Self
182    }
183
184    fn on_activate(&mut self, mut world: World) -> World {
185        let data = get_data_mut(&mut world);
186        if let Some(data) = trace_ok_err(data) {
187            data.menu_active = true;
188        }
189        file_change(world)
190    }
191    fn on_deactivate(&mut self, mut world: World) -> World {
192        let data = get_data_mut(&mut world);
193        if let Some(data) = trace_ok_err(data) {
194            data.menu_active = false;
195        }
196        world
197    }
198    fn on_filechange(&mut self, world: World, history: History) -> (World, History) {
199        (file_change(world), history)
200    }
201    fn events_tf(
202        &mut self,
203        mut world: World,
204        mut history: History,
205        _event: &Events,
206    ) -> (World, History) {
207        let is_addition_triggered = get_specific(&world).map(|d| d.options.is_addition_triggered);
208        if is_addition_triggered == Some(true) {
209            // handle addition triggered in the GUI
210            (world, history) = add_attribute(world, history, false);
211        }
212        let attr_data = get_specific_mut(&mut world);
213        if let Some(attr_data) = attr_data
214            && let Some(rename_src_idx) = attr_data.options.rename_src_idx
215        {
216            let from_name = &attr_data.attr_names()[rename_src_idx].clone();
217            let to_name = &attr_data.new_attr_name.clone();
218            tracing::info!("Rename attribute {from_name} to {to_name}");
219            attr_data.rename(from_name, to_name);
220            attr_data.options.rename_src_idx = None;
221        }
222        let is_update_triggered = get_specific(&world).map(|d| d.options.is_update_triggered);
223        if is_update_triggered == Some(true) {
224            info!("update attr");
225            let current_from_menu_clone =
226                get_specific(&world).and_then(|d| d.current_attr_map.clone());
227            if let (Some(mut cfm), Some(anno)) =
228                (current_from_menu_clone, get_annos_mut(&mut world))
229            {
230                *anno = mem::take(&mut cfm);
231            }
232            if let Some(update_current_attr_map) =
233                get_specific_mut(&mut world).map(|d| &mut d.options.is_update_triggered)
234            {
235                *update_current_attr_map = false;
236            }
237        }
238        (world, history) = check_remove(world, history);
239
240        let is_export_triggered =
241            get_specific(&world).map(|d| d.options.import_export_trigger.export_triggered());
242        if is_export_triggered == Some(true) {
243            let ssh_cfg = world.data.meta_data.ssh_cfg.clone();
244            let attr_data = get_specific(&world);
245            let export_only_opened_folder =
246                attr_data.map(|d| d.options.export_only_opened_folder) == Some(true);
247            let key_filter = if export_only_opened_folder {
248                world
249                    .data
250                    .meta_data
251                    .opened_folder
252                    .as_ref()
253                    .map(PathPair::path_relative)
254            } else {
255                None
256            };
257            let annos_str = get_specific(&world)
258                .and_then(|d| trace_ok_err(d.serialize_annotations(key_filter)));
259            if let (Some(annos_str), Some(data)) = (annos_str, get_specific(&world))
260                && trace_ok_err(data.export_path.conn.write(
261                    &annos_str,
262                    &data.export_path.path,
263                    ssh_cfg.as_ref(),
264                ))
265                .is_some()
266            {
267                info!("exported annotations to {:?}", data.export_path.path);
268            }
269
270            if let Some(export_triggered) =
271                get_specific_mut(&mut world).map(|d| &mut d.options.import_export_trigger)
272            {
273                export_triggered.untrigger_export();
274            }
275        }
276        let is_import_triggered =
277            get_specific(&world).map(|d| d.options.import_export_trigger.import_triggered());
278        if is_import_triggered == Some(true) {
279            tracing::info!("import attr tiggered");
280            let ssh_cfg = world.data.meta_data.ssh_cfg.clone();
281            let cur_prj = world.data.meta_data.prj_path().map(|p| p.to_path_buf());
282            let attr_data = get_specific_mut(&mut world);
283            let imported_map = attr_data.and_then(|data| {
284                let in_path = &data.export_path.path;
285                tracing::info!("importing attributes from {in_path:?}");
286                let json_str = trace_ok_err(data.export_path.conn.read(in_path, ssh_cfg.as_ref()));
287                if let Some(s) = json_str {
288                    trace_ok_err(AttributesToolData::deserialize_annotations(
289                        &s,
290                        cur_prj.as_deref(),
291                    ))
292                } else {
293                    None
294                }
295            });
296            if let Some(imported_map) = &imported_map {
297                // add attributes in case they don't exist
298                for (_, (attr_map, _)) in imported_map.iter() {
299                    for (attr_name, attr_val) in attr_map.iter() {
300                        let data = get_specific_mut(&mut world);
301                        if let Some(d) = data {
302                            d.new_attr_name = attr_name.clone();
303                            d.new_attr_val = attr_val.clone().reset();
304                        }
305                        tracing::debug!("inserting attr {attr_name} with value {attr_val}");
306                        (world, history) = add_attribute(world, history, true);
307                    }
308                }
309            }
310            if let Some(imported_map) = imported_map {
311                let data = get_specific_mut(&mut world);
312                if let Some(d) = data {
313                    d.merge_map(imported_map);
314                }
315            }
316            let annos = get_annos(&world).cloned();
317            let attr_buffer = get_buffers(&world);
318            if let (Some(data), Some(annos)) = (get_specific_mut(&mut world), annos) {
319                data.current_attr_map = Some(annos);
320                data.set_new_attr_value_buffer(attr_buffer);
321            }
322        }
323        if let Some(import_trigger) =
324            get_specific_mut(&mut world).map(|d| &mut d.options.import_export_trigger)
325        {
326            import_trigger.untrigger_import();
327        }
328        make_tool_transform!(self, world, history, event, [])
329    }
330}
331#[cfg(test)]
332use {
333    crate::tracing_setup::init_tracing_for_tests,
334    crate::types::{ThumbIms, ViewImage},
335    image::DynamicImage,
336    std::collections::HashMap,
337    std::fs,
338    std::path::Path,
339};
340#[cfg(test)]
341pub(super) fn test_data() -> (World, History) {
342    use std::path::Path;
343
344    use crate::ToolsDataMap;
345
346    let im_test = DynamicImage::ImageRgb8(ViewImage::new(64, 64));
347    let mut world = World::from_real_im(
348        im_test,
349        ThumbIms::default(),
350        ToolsDataMap::new(),
351        None,
352        Some("superimage.png".to_string()),
353        Path::new("superimage.png"),
354        Some(0),
355    );
356    world.data.meta_data.flags.is_loading_screen_active = Some(false);
357
358    let history = History::default();
359    (world, history)
360}
361#[test]
362fn test_import_export() {
363    init_tracing_for_tests();
364    fn test(testpath: &Path) {
365        let (mut world, history) = test_data();
366        let data = get_specific_mut(&mut world).unwrap();
367        let json_str = fs::read_to_string(testpath).unwrap();
368        let reference_data = AttributesToolData::deserialize_annotations(&json_str, None).unwrap();
369        tracing::debug!("reference_data: {:?}", reference_data);
370        data.export_path.path = testpath.to_path_buf();
371        data.options.import_export_trigger.trigger_import();
372        let events = Events::default();
373        let (world, _) = Attributes {}.events_tf(world, history, &events);
374        let annos = world.data.tools_data_map[ACTOR_NAME]
375            .specifics
376            .attributes()
377            .unwrap()
378            .anno_iter()
379            .collect::<HashMap<_, _>>();
380        tracing::debug!("annos: {:?}", annos);
381        for k in reference_data.keys() {
382            tracing::debug!("k: {:?}", k);
383            let (annos, _) = annos.get(k).unwrap();
384            let (ref_annos, _) = &reference_data[k];
385            assert_eq!(annos, ref_annos);
386        }
387        let current = get_annos(&world).unwrap();
388        for v in current.values() {
389            assert!(v.is_default());
390        }
391    }
392    let testpath = Path::new("resources/test_data/attr_import.json");
393    test(testpath);
394    let testpath = Path::new("resources/test_data/attr_import_untagged.json");
395    test(testpath);
396}
397
398#[test]
399fn test_add() {
400    init_tracing_for_tests();
401    let mut attr_tool = Attributes::new();
402    let events = Events::default();
403    let (mut world, history) = test_data();
404    let attr_data = get_specific_mut(&mut world).unwrap();
405    attr_data.options.is_addition_triggered = true;
406    attr_data.new_attr_name = "a attr".to_string();
407    attr_data.new_attr_val = ParamVal::Int(Some(1));
408    let (mut world, history) = attr_tool.events_tf(world, history, &events);
409    let attr_data = get_specific_mut(&mut world).unwrap();
410    attr_data.options.is_addition_triggered = true;
411    attr_data.new_attr_name = "c attr".to_string();
412    attr_data.new_attr_val = ParamVal::Int(Some(2));
413    let (mut world, history) = attr_tool.events_tf(world, history, &events);
414    let attr_data = get_specific_mut(&mut world).unwrap();
415    attr_data.options.is_addition_triggered = true;
416    attr_data.new_attr_name = "b attr".to_string();
417    attr_data.new_attr_val = ParamVal::Int(Some(3));
418    let (world, _) = attr_tool.events_tf(world, history, &events);
419    let data = get_specific(&world).unwrap();
420    let cam = data.current_attr_map.as_ref().unwrap();
421    let c_attr_val = cam.get("c attr").unwrap();
422    assert_eq!(c_attr_val, &ParamVal::Int(Some(2)));
423    let b_attr_val = cam.get("b attr").unwrap();
424    assert_eq!(b_attr_val, &ParamVal::Int(Some(3)));
425    let a_attr_val = cam.get("a attr").unwrap();
426    assert_eq!(a_attr_val, &ParamVal::Int(Some(1)));
427
428    // cur map is a BTree and hence sorted
429    assert_eq!(data.attr_names(), &["a attr", "b attr", "c attr"]);
430    assert_eq!(
431        data.attr_vals(),
432        &[
433            ParamVal::Int(Some(1)),
434            ParamVal::Int(Some(3)),
435            ParamVal::Int(Some(2)),
436        ]
437    );
438}
439#[test]
440fn test_rm_add() {
441    init_tracing_for_tests();
442    let (mut world, history) = test_data();
443    let attr_data = get_specific_mut(&mut world).unwrap();
444    attr_data.options.is_addition_triggered = true;
445    attr_data.new_attr_name = "test_attr".to_string();
446    attr_data.new_attr_val = ParamVal::Str("123".into());
447    let (mut world, history) = add_attribute(world, history, false);
448    let attr_data = get_specific_mut(&mut world).unwrap();
449    attr_data.options.removal_idx = Some(0);
450    let (mut world, _) = check_remove(world, history);
451    let attr_data = get_specific_mut(&mut world).unwrap();
452    assert!(!attr_data.options.is_addition_triggered);
453    assert!(attr_data.options.removal_idx.is_none());
454    assert_eq!(
455        attr_data.current_attr_map.as_ref().map(|cam| cam.len()),
456        Some(0)
457    );
458}