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