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