Skip to main content

libsurfer/
wave_data.rs

1use std::collections::HashMap;
2
3use egui::{Id, Pos2};
4use eyre::{Result, WrapErr as _};
5use num::bigint::ToBigInt as _;
6use num::{BigInt, BigUint, One, ToPrimitive, Zero};
7use serde::{Deserialize, Serialize};
8use surfer_translation_types::{TranslationPreference, Translator, VariableValue};
9use tracing::{error, info, warn};
10
11use crate::annotation::{Annotatable, Annotation};
12use crate::annotation_list::AnnotationGroup;
13use crate::data_container::DataContainer;
14use crate::displayed_item::{
15    DisplayedDivider, DisplayedFieldRef, DisplayedGroup, DisplayedItem, DisplayedItemRef,
16    DisplayedStream, DisplayedTimeLine, DisplayedVariable,
17};
18use crate::displayed_item_tree::{DisplayedItemTree, ItemIndex, TargetPosition, VisibleItemIndex};
19use crate::graphics::{Graphic, GraphicId};
20use crate::transaction_container::{StreamScopeRef, TransactionRef, TransactionStreamRef};
21use crate::transactions::calculate_rows_of_stream;
22use crate::translation::{DynTranslator, TranslatorList, VariableInfoExt};
23use crate::variable_name_type::VariableNameType;
24use crate::view::{DrawingContext, ItemDrawingInfo};
25use crate::viewport::Viewport;
26use crate::wave_container::{
27    AnalogCacheKey, ScopeRef, ScopeRefExt as _, VariableMeta, VariableRef, VariableRefExt,
28    WaveContainer,
29};
30use crate::wave_source::{WaveFormat, WaveSource};
31use crate::wellen::LoadSignalsCmd;
32use ftr_parser::types::{StreamId, Transaction};
33use itertools::Itertools;
34use std::fmt::Formatter;
35use std::ops::Not;
36
37pub const PER_SCROLL_EVENT: f32 = 50.0;
38pub const SCROLL_EVENTS_PER_PAGE: f32 = 20.0;
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub enum ScopeType {
42    WaveScope(ScopeRef),
43    StreamScope(StreamScopeRef),
44}
45
46impl std::fmt::Display for ScopeType {
47    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48        match self {
49            ScopeType::WaveScope(w) => w.fmt(f),
50            ScopeType::StreamScope(s) => s.fmt(f),
51        }
52    }
53}
54
55#[derive(Serialize, Deserialize)]
56pub struct WaveData {
57    #[serde(skip, default = "DataContainer::__new_empty")]
58    pub inner: DataContainer,
59    pub source: WaveSource,
60    pub format: WaveFormat,
61    pub active_scope: Option<ScopeType>,
62    /// Root items (variables, dividers, ...) to display
63    pub items_tree: DisplayedItemTree,
64    pub displayed_items: HashMap<DisplayedItemRef, DisplayedItem>,
65    /// Tracks the consecutive displayed item refs
66    pub display_item_ref_counter: usize,
67    pub viewports: Vec<Viewport>,
68    pub cursor: Option<BigInt>,
69    pub markers: HashMap<u8, BigInt>,
70    #[serde(default)]
71    pub selected_annotation: Option<Id>,
72
73    #[serde(default)]
74    pub annotations: Vec<Annotation>,
75    pub annotation_groups: Vec<AnnotationGroup>, // List of unique group names
76    pub annotation_list_visible: bool,
77    #[serde(default)]
78    pub annotation_counter: i32,
79    pub last_active_viewport_idx: usize,
80    #[serde(skip, default)]
81    pub(crate) annotation_menu_pos: Option<Pos2>,
82    #[serde(skip, default)]
83    pub annotation_menu_time: Option<BigInt>,
84
85    pub focused_item: Option<VisibleItemIndex>,
86    pub focused_transaction: (Option<TransactionRef>, Option<Transaction>),
87    pub default_variable_name_type: VariableNameType,
88    pub scroll_offset: f32,
89    pub display_variable_indices: bool,
90    pub graphics: HashMap<GraphicId, Graphic>,
91    /// These are just stored during operation, so no need to serialize
92    #[serde(skip)]
93    pub drawing_infos: Vec<ItemDrawingInfo>,
94    #[serde(skip)]
95    pub top_item_draw_offset: f32,
96    #[serde(skip)]
97    pub total_height: f32,
98    #[serde(skip)]
99    pub old_num_timestamps: Option<BigInt>,
100    /// Generation counter for analog cache invalidation on waveform reload.
101    #[serde(skip)]
102    pub cache_generation: u64,
103    /// Registry of in-flight analog cache builds for sharing.
104    /// Cleared on waveform reload when generation changes.
105    #[serde(skip)]
106    pub inflight_caches:
107        HashMap<AnalogCacheKey, std::sync::Arc<crate::analog_signal_cache::AnalogCacheEntry>>,
108}
109
110fn select_preferred_translator(var: &VariableMeta, translators: &TranslatorList) -> String {
111    let mut preferred: Vec<_> = translators
112        .all_translators()
113        .iter()
114        .filter_map(|t| match t.translates(var) {
115            Ok(TranslationPreference::Prefer) => Some(t.name()),
116            Ok(TranslationPreference::Yes) => None,
117            Ok(TranslationPreference::No) => None,
118            Err(e) => {
119                error!(
120                    "Failed to check if {} translates {}\n{e:#?}",
121                    t.name(),
122                    var.var.full_path_string_no_index()
123                );
124                None
125            }
126        })
127        .collect();
128    if preferred.len() > 1 {
129        // For a single bit that has other preferred translators in addition to "Bit", like enum,
130        // we would like to select the other one.
131        let bit = "Bit".to_string();
132        if var.num_bits == Some(1) {
133            preferred.retain(|x| x != &bit);
134        }
135        if preferred.len() > 1 {
136            warn!(
137                "More than one preferred translator for variable {} in scope {}: {}",
138                var.var.name,
139                var.var.path.full_name(),
140                preferred.join(", ")
141            );
142            preferred.sort();
143        }
144    }
145    // make sure we always pick the same translator, at least
146    preferred
147        .pop()
148        .unwrap_or_else(|| translators.default.clone())
149}
150
151pub fn variable_translator<'a, F>(
152    translator: Option<&String>,
153    field: &[String],
154    translators: &'a TranslatorList,
155    meta: F,
156) -> &'a DynTranslator
157where
158    F: FnOnce() -> Result<VariableMeta>,
159{
160    let translator_name = translator.cloned().unwrap_or_else(|| {
161        if field.is_empty() {
162            meta().as_ref().map_or_else(
163                |e| {
164                    warn!("{e:#?}");
165                    translators.default.clone()
166                },
167                |meta| select_preferred_translator(meta, translators).clone(),
168            )
169        } else {
170            translators.default.clone()
171        }
172    });
173
174    (translators.get_translator(&translator_name)) as _
175}
176
177impl WaveData {
178    #[must_use]
179    pub fn update_with_waves(
180        mut self,
181        new_waves: Box<WaveContainer>,
182        source: WaveSource,
183        format: WaveFormat,
184        translators: &TranslatorList,
185        keep_unavailable: bool,
186    ) -> (WaveData, Option<LoadSignalsCmd>) {
187        let active_scope = self.active_scope.take().filter(|m| {
188            if let ScopeType::WaveScope(w) = m {
189                new_waves.scope_exists(w)
190            } else {
191                false
192            }
193        });
194        let display_items = Self::update_displayed_items(
195            &new_waves,
196            &self.displayed_items,
197            keep_unavailable,
198            translators,
199            &mut self.items_tree,
200        );
201
202        let old_num_timestamps = self.num_timestamps();
203        let mut new_wavedata = WaveData {
204            inner: DataContainer::Waves(*new_waves),
205            source,
206            format,
207            active_scope,
208            items_tree: self.items_tree,
209            displayed_items: display_items,
210            display_item_ref_counter: self.display_item_ref_counter,
211            viewports: self.viewports,
212            cursor: self.cursor.clone(),
213            markers: self.markers.clone(),
214            annotations: self.annotations.clone(),
215            selected_annotation: None,
216            annotation_groups: Vec::new(), // List of unique group names
217            annotation_list_visible: false,
218            annotation_counter: self.annotation_counter,
219            last_active_viewport_idx: 0,
220            annotation_menu_pos: None,
221            annotation_menu_time: None,
222            focused_item: self.focused_item,
223            focused_transaction: self.focused_transaction,
224            default_variable_name_type: self.default_variable_name_type,
225            display_variable_indices: self.display_variable_indices,
226            scroll_offset: self.scroll_offset,
227            drawing_infos: vec![],
228            top_item_draw_offset: 0.,
229            graphics: HashMap::new(),
230            total_height: 0.,
231            old_num_timestamps,
232            cache_generation: self.cache_generation + 1, // Invalidate all existing caches
233            inflight_caches: HashMap::new(),
234        };
235
236        new_wavedata.update_metadata(translators);
237        let load_commands = new_wavedata.load_waves();
238        (new_wavedata, load_commands)
239    }
240
241    pub fn update_with_items(
242        &mut self,
243        new_items: &HashMap<DisplayedItemRef, DisplayedItem>,
244        mut items_tree: DisplayedItemTree,
245        translators: &TranslatorList,
246    ) -> Option<LoadSignalsCmd> {
247        self.displayed_items = Self::update_displayed_items(
248            self.inner.as_waves().unwrap(),
249            new_items,
250            true,
251            translators,
252            &mut items_tree,
253        );
254        self.items_tree = items_tree;
255
256        self.display_item_ref_counter = self
257            .displayed_items
258            .keys()
259            .map(|dir| dir.0)
260            .max()
261            .unwrap_or(0);
262
263        self.update_metadata(translators);
264        self.load_waves()
265    }
266
267    /// Go through all signals and update the metadata for all signals
268    ///
269    /// Used after loading new waves, signals or switching a bunch of translators
270    fn update_metadata(&mut self, translators: &TranslatorList) {
271        for di in self.displayed_items.values_mut() {
272            let DisplayedItem::Variable(displayed_variable) = di else {
273                continue;
274            };
275
276            let meta = self
277                .inner
278                .as_waves()
279                .unwrap()
280                .variable_meta(&displayed_variable.variable_ref.clone())
281                .unwrap();
282            let translator =
283                variable_translator(displayed_variable.get_format(&[]), &[], translators, || {
284                    Ok(meta.clone())
285                });
286            let info = translator.variable_info(&meta).ok();
287
288            match info {
289                Some(info) => displayed_variable
290                    .field_formats
291                    .retain(|ff| info.has_subpath(&ff.field)),
292                _ => displayed_variable.field_formats.clear(),
293            }
294
295            displayed_variable.downgrade_type_limits_if_unsupported(translator, &meta);
296        }
297    }
298
299    /// Get the underlying wave container to load all signals that are being displayed
300    ///
301    /// This is needed for wave containers that lazy-load signals.
302    fn load_waves(&mut self) -> Option<LoadSignalsCmd> {
303        let variables = self.displayed_items.values().filter_map(|item| match item {
304            DisplayedItem::Variable(r) => Some(&r.variable_ref),
305            _ => None,
306        });
307        self.inner
308            .as_waves_mut()
309            .unwrap()
310            .load_variables(variables)
311            .expect("internal error: failed to load variables")
312    }
313
314    /// Needs to be called after `update_with`, once the new number of timestamps is available in
315    /// the inner `WaveContainer`.
316    pub fn update_viewports(&mut self) {
317        if let Some(old_num_timestamps) = std::mem::take(&mut self.old_num_timestamps) {
318            // FIXME: I'm not sure if Defaulting to 1 time step is the right thing to do if we
319            // have none, but it does avoid some potentially nasty division by zero problems
320            let new_num_timestamps = self
321                .inner
322                .max_timestamp()
323                .unwrap_or_else(BigUint::one)
324                .to_bigint()
325                .unwrap();
326            if new_num_timestamps != old_num_timestamps {
327                for viewport in &mut self.viewports {
328                    *viewport = viewport.clip_to(&old_num_timestamps, &new_num_timestamps);
329                }
330            }
331        }
332    }
333
334    fn update_displayed_items(
335        waves: &WaveContainer,
336        items: &HashMap<DisplayedItemRef, DisplayedItem>,
337        keep_unavailable: bool,
338        translators: &TranslatorList,
339        items_tree: &mut DisplayedItemTree,
340    ) -> HashMap<DisplayedItemRef, DisplayedItem> {
341        items
342            .iter()
343            .filter_map(|(&id, i)| {
344                let new = match i {
345                    // keep without a change
346                    DisplayedItem::Divider(_)
347                    | DisplayedItem::Marker(_)
348                    | DisplayedItem::TimeLine(_)
349                    | DisplayedItem::Stream(_)
350                    | DisplayedItem::Group(_) => Some((id, i.clone())),
351                    DisplayedItem::Variable(s) => {
352                        s.update(waves, keep_unavailable).map(|r| (id, r))
353                    }
354                    DisplayedItem::Placeholder(p) => {
355                        match waves.update_variable_ref(&p.variable_ref) {
356                            None => {
357                                if keep_unavailable {
358                                    Some((id, DisplayedItem::Placeholder(p.clone())))
359                                } else {
360                                    None
361                                }
362                            }
363                            Some(new_variable_ref) => {
364                                let Ok(meta) = waves
365                                    .variable_meta(&new_variable_ref)
366                                    .context("When updating")
367                                    .map_err(|e| error!("{e:#?}"))
368                                else {
369                                    return Some((id, DisplayedItem::Placeholder(p.clone())));
370                                };
371                                let translator = variable_translator(
372                                    p.format.as_ref(),
373                                    &[],
374                                    translators,
375                                    || Ok(meta.clone()),
376                                );
377                                let info = translator.variable_info(&meta).unwrap();
378                                Some((
379                                    id,
380                                    DisplayedItem::Variable(
381                                        p.clone().into_variable(info, new_variable_ref),
382                                    ),
383                                ))
384                            }
385                        }
386                    }
387                };
388
389                // remove element from item_tree if we are about to remove it from the displayed_items
390                // we only remove variables or placeholders, so we don't have to think about traversing
391                if new.is_none() {
392                    let removed = items_tree.drain_recursive_if(|n| n.item_ref == id);
393                    assert!(
394                        removed.len() <= 1,
395                        "more elements removed then should be possible"
396                    );
397                }
398
399                new
400            })
401            .collect()
402    }
403
404    #[must_use]
405    pub fn select_preferred_translator(
406        &self,
407        var: &VariableMeta,
408        translators: &TranslatorList,
409    ) -> String {
410        select_preferred_translator(var, translators)
411    }
412
413    #[must_use]
414    pub fn variable_translator<'a>(
415        &'a self,
416        field: &DisplayedFieldRef,
417        translators: &'a TranslatorList,
418    ) -> &'a DynTranslator {
419        let Some(DisplayedItem::Variable(displayed_variable)) =
420            self.displayed_items.get(&field.item)
421        else {
422            panic!("asking for translator for a non DisplayItem::Variable item")
423        };
424
425        variable_translator(
426            displayed_variable.get_format(&field.field),
427            &field.field,
428            translators,
429            || {
430                self.inner
431                    .as_waves()
432                    .unwrap()
433                    .variable_meta(&displayed_variable.variable_ref)
434            },
435        )
436    }
437
438    #[must_use]
439    pub fn variable_translator_with_meta<'a>(
440        &'a self,
441        field: &DisplayedFieldRef,
442        translators: &'a TranslatorList,
443        meta: &VariableMeta,
444    ) -> &'a DynTranslator {
445        let Some(DisplayedItem::Variable(displayed_variable)) =
446            self.displayed_items.get(&field.item)
447        else {
448            panic!("asking for translator for a non DisplayItem::Variable item")
449        };
450
451        variable_translator(
452            displayed_variable.get_format(&field.field),
453            &field.field,
454            translators,
455            || Ok(meta.clone()),
456        )
457    }
458
459    pub fn add_variables(
460        &mut self,
461        translators: &TranslatorList,
462        variables: Vec<VariableRef>,
463        target_position: Option<TargetPosition>,
464        update_display_names: bool,
465        ignore_failures: bool,
466        variable_name_type: Option<VariableNameType>,
467    ) -> (Option<LoadSignalsCmd>, Vec<DisplayedItemRef>) {
468        let mut indices = vec![];
469        // load variables from waveform
470        let res = match self
471            .inner
472            .as_waves_mut()
473            .unwrap()
474            .load_variables(variables.iter())
475        {
476            Err(e) => {
477                error!("{e:#?}");
478                return (None, indices);
479            }
480            Ok(res) => res,
481        };
482
483        // initialize translator and add display item
484        let mut target_position = target_position
485            .or_else(|| self.insert_position(self.focused_item))
486            .unwrap_or(self.end_insert_position());
487        for variable in variables {
488            let Ok(meta) = self
489                .inner
490                .as_waves()
491                .unwrap()
492                .variable_meta(&variable)
493                .context("When adding variable")
494                .map_err(|e| error!("{e:#?}"))
495            else {
496                if ignore_failures {
497                    continue;
498                }
499                return (res, indices);
500            };
501
502            let translator = variable_translator(None, &[], translators, || Ok(meta.clone()));
503            let info = translator.variable_info(&meta).unwrap();
504
505            let new_variable = DisplayedItem::Variable(DisplayedVariable {
506                variable_ref: variable.clone(),
507                info,
508                color: None,
509                background_color: None,
510                display_name: variable.name.clone(),
511                display_name_type: variable_name_type.unwrap_or(self.default_variable_name_type),
512                manual_name: None,
513                format: None,
514                field_formats: vec![],
515                height_scaling_factor: None,
516                analog: None,
517            });
518
519            indices.push(self.insert_item(new_variable, Some(target_position), true));
520            target_position = TargetPosition {
521                before: ItemIndex(target_position.before.0 + 1),
522                level: target_position.level,
523            }
524        }
525
526        if update_display_names {
527            self.compute_variable_display_names();
528        }
529        (res, indices)
530    }
531
532    /// Remove a single item, it's legal to call this function with an invalid ID
533    pub fn remove_displayed_item(&mut self, id: DisplayedItemRef) {
534        let Some(idx) = self
535            .items_tree
536            .iter()
537            .enumerate()
538            .find(|(_, node)| node.item_ref == id)
539            .map(|(idx, _)| ItemIndex(idx))
540        else {
541            return;
542        };
543
544        let focused_item_ref = self
545            .focused_item
546            .and_then(|vidx| self.items_tree.get_visible(vidx))
547            .map(|node| node.item_ref);
548
549        for removed_ref in self.items_tree.remove_recursive(idx) {
550            if let Some(DisplayedItem::Marker(m)) = self.displayed_items.remove(&removed_ref) {
551                self.markers.remove(&m.idx);
552            }
553
554            self.annotations
555                .retain(|annotation| !annotation.is_attached(&removed_ref));
556        }
557
558        self.focused_item = focused_item_ref.and_then(|focused_item_ref| {
559            match self
560                .items_tree
561                .iter_visible()
562                .find_position(|node| node.item_ref == focused_item_ref)
563                .map(|(vidx, _)| VisibleItemIndex(vidx))
564            {
565                Some(vidx) => Some(vidx),
566                None if self
567                    .focused_item
568                    .and_then(|focused_vidx| self.items_tree.to_displayed(focused_vidx))
569                    .is_some() =>
570                {
571                    Some(self.focused_item.unwrap())
572                }
573                None => self
574                    .items_tree
575                    .iter_visible()
576                    .count()
577                    .checked_sub(1)
578                    .map(VisibleItemIndex),
579            }
580        });
581    }
582
583    pub fn add_divider(&mut self, name: Option<String>, vidx: Option<VisibleItemIndex>) {
584        self.insert_item(
585            DisplayedItem::Divider(DisplayedDivider {
586                color: None,
587                background_color: None,
588                name,
589            }),
590            self.insert_position(vidx),
591            true,
592        );
593    }
594
595    pub fn add_timeline(&mut self, vidx: Option<VisibleItemIndex>) {
596        self.insert_item(
597            DisplayedItem::TimeLine(DisplayedTimeLine {
598                color: None,
599                background_color: None,
600                name: None,
601            }),
602            self.insert_position(vidx),
603            true,
604        );
605    }
606
607    pub fn add_group(
608        &mut self,
609        name: String,
610        target_position: Option<TargetPosition>,
611    ) -> DisplayedItemRef {
612        self.insert_item(
613            DisplayedItem::Group(DisplayedGroup {
614                name,
615                color: None,
616                background_color: None,
617                content: vec![],
618                is_open: false,
619            }),
620            target_position,
621            true,
622        )
623    }
624
625    pub fn select_annotation(&mut self, id: Option<Id>) {
626        self.selected_annotation = id;
627    }
628
629    pub fn add_generator(&mut self, gen_ref: TransactionStreamRef) {
630        let Some(gen_id) = gen_ref.gen_id else { return };
631        let Some(transactions) = self.inner.as_transactions_mut() else {
632            return;
633        };
634        let is_empty = {
635            let Some(generator) = transactions.get_generator(gen_id) else {
636                return;
637            };
638            generator.transactions.is_empty()
639        };
640        if is_empty {
641            info!("(Generator {gen_id}) Loading transactions into memory!");
642            match transactions
643                .inner
644                .load_stream_into_memory(gen_ref.stream_id)
645            {
646                Ok(()) => info!("(Generator {gen_id}) Finished loading transactions!"),
647                Err(_) => return,
648            }
649        }
650
651        let mut last_times_on_row = vec![(BigUint::ZERO, BigUint::ZERO)];
652        let Some(generator) = transactions.get_generator(gen_id) else {
653            return;
654        };
655        calculate_rows_of_stream(&generator.transactions, &mut last_times_on_row);
656
657        let new_gen = DisplayedItem::Stream(DisplayedStream {
658            display_name: gen_ref.name.clone(),
659            transaction_stream_ref: gen_ref,
660            color: None,
661            background_color: None,
662            manual_name: None,
663            rows: last_times_on_row.len(),
664        });
665
666        self.insert_item(new_gen, None, true);
667    }
668
669    pub fn add_stream(&mut self, stream_ref: TransactionStreamRef) {
670        if self
671            .inner
672            .as_transactions_mut()
673            .unwrap()
674            .get_stream(stream_ref.stream_id)
675            .unwrap()
676            .transactions_loaded
677            .not()
678        {
679            info!("(Stream) Loading transactions into memory!");
680            match self
681                .inner
682                .as_transactions_mut()
683                .unwrap()
684                .inner
685                .load_stream_into_memory(stream_ref.stream_id)
686            {
687                Ok(()) => info!(
688                    "(Stream {}) Finished loading transactions!",
689                    stream_ref.stream_id
690                ),
691                Err(_) => return,
692            }
693        }
694
695        let stream = self
696            .inner
697            .as_transactions()
698            .unwrap()
699            .get_stream(stream_ref.stream_id)
700            .unwrap();
701        let mut last_times_on_row = vec![(BigUint::ZERO, BigUint::ZERO)];
702
703        for gen_id in &stream.generators {
704            let generator = self
705                .inner
706                .as_transactions()
707                .unwrap()
708                .get_generator(*gen_id)
709                .unwrap();
710            calculate_rows_of_stream(&generator.transactions, &mut last_times_on_row);
711        }
712
713        let new_stream = DisplayedItem::Stream(DisplayedStream {
714            display_name: stream_ref.name.clone(),
715            transaction_stream_ref: stream_ref,
716            color: None,
717            background_color: None,
718            manual_name: None,
719            rows: last_times_on_row.len(),
720        });
721
722        self.insert_item(new_stream, None, true);
723    }
724
725    pub fn add_all_streams(&mut self) {
726        let mut streams: Vec<(StreamId, String)> = vec![];
727        for stream in self.inner.as_transactions().unwrap().get_streams() {
728            streams.push((stream.id, stream.name.clone()));
729        }
730
731        for (id, name) in streams
732            .into_iter()
733            .sorted_by(|a, b| numeric_sort::cmp(&a.1, &b.1))
734        {
735            self.add_stream(TransactionStreamRef::new_stream(id, name));
736        }
737    }
738
739    /// Return an insert position based on item
740    ///
741    /// If an item is passed, and it is
742    /// - an unfolded group, insert index is to the first element of the group
743    /// - a folded group, insert index is to before the next sibling (if exists)
744    /// - otherwise insert index is past it on the same level
745    #[must_use]
746    pub fn insert_position(&self, vidx: Option<VisibleItemIndex>) -> Option<TargetPosition> {
747        let vidx = vidx?;
748        let item_index = self.items_tree.to_displayed(vidx)?;
749        let node = self.items_tree.get(item_index)?;
750        let item = self.displayed_items.get(&node.item_ref)?;
751
752        // TODO add get_next_sibling to tree?
753        let (before, level) = match item {
754            DisplayedItem::Group(..) if node.unfolded => (item_index.0 + 1, node.level + 1),
755            DisplayedItem::Group(..) => {
756                let next_idx = self.items_tree.to_displayed(VisibleItemIndex(vidx.0 + 1));
757                match next_idx {
758                    Some(idx) => (idx.0, node.level),
759                    None => (self.items_tree.len(), node.level),
760                }
761            }
762            _ => (item_index.0 + 1, node.level),
763        };
764        Some(TargetPosition {
765            before: ItemIndex(before),
766            level,
767        })
768    }
769
770    /// Return insert position as last item
771    #[must_use]
772    pub fn end_insert_position(&self) -> TargetPosition {
773        TargetPosition {
774            before: ItemIndex(self.items_tree.len()),
775            level: 0,
776        }
777    }
778
779    #[must_use]
780    pub fn index_for_ref_or_focus(&self, item_ref: Option<DisplayedItemRef>) -> Option<ItemIndex> {
781        if let Some(item_ref) = item_ref {
782            self.items_tree
783                .iter()
784                .enumerate()
785                .find_map(|(idx, node)| (node.item_ref == item_ref).then_some(ItemIndex(idx)))
786        } else if let Some(focused_item) = self.focused_item {
787            self.items_tree
788                .get_visible_extra(focused_item)
789                .map(|info| info.idx)
790        } else {
791            None
792        }
793    }
794
795    /// Insert item after item vidx if Some(vidx).
796    /// If None, insert in relation to focused item (see [`Self::focused_insert_position()`]).
797    /// If nothing is selected, fall back to appending.
798    /// Focus on the inserted item if there was a focused item.
799    pub(crate) fn insert_item(
800        &mut self,
801        new_item: DisplayedItem,
802        target_position: Option<TargetPosition>,
803        move_focus: bool,
804    ) -> DisplayedItemRef {
805        let target_position = target_position
806            .or_else(|| self.insert_position(self.focused_item))
807            .unwrap_or_else(|| self.end_insert_position());
808
809        let item_ref = self.next_displayed_item_ref();
810        let insert_index = self
811            .items_tree
812            .insert_item(item_ref, target_position)
813            .unwrap();
814        self.displayed_items.insert(item_ref, new_item);
815        if move_focus {
816            self.focused_item = self.focused_item.and_then(|_| {
817                self.items_tree
818                    .iter_visible_extra()
819                    .find_map(|info| (info.idx == insert_index).then_some(info.vidx))
820            });
821        }
822        self.items_tree.xselect_all_visible(false);
823        item_ref
824    }
825
826    pub fn go_to_cursor_if_not_in_view(&mut self) -> bool {
827        if let Some(cursor) = &self.cursor {
828            let num_timestamps = self.safe_num_timestamps();
829            self.viewports[0].go_to_cursor_if_not_in_view(cursor, &num_timestamps)
830        } else {
831            false
832        }
833    }
834
835    #[inline]
836    #[must_use]
837    pub fn numbered_marker_location(&self, idx: u8, viewport: &Viewport, view_width: f32) -> f32 {
838        viewport.pixel_from_time(
839            self.numbered_marker_time(idx),
840            view_width,
841            &self.safe_num_timestamps(),
842        )
843    }
844
845    #[inline]
846    #[must_use]
847    pub fn numbered_marker_time(&self, idx: u8) -> &BigInt {
848        self.markers.get(&idx).unwrap()
849    }
850
851    #[must_use]
852    pub fn viewport_all(&self) -> Viewport {
853        Viewport::new()
854    }
855
856    pub fn remove_placeholders(&mut self) {
857        let removed_refs = self.items_tree.drain_recursive_if(|node| {
858            matches!(
859                self.displayed_items.get(&node.item_ref),
860                Some(DisplayedItem::Placeholder(_))
861            )
862        });
863        for removed_ref in removed_refs {
864            self.displayed_items.remove(&removed_ref);
865        }
866    }
867
868    #[inline]
869    #[must_use]
870    pub fn any_displayed(&self) -> bool {
871        !self.displayed_items.is_empty()
872    }
873
874    fn drawing_top(&self) -> Option<f32> {
875        self.drawing_infos
876            .iter()
877            .map(ItemDrawingInfo::top)
878            .min_by(f32::total_cmp)
879    }
880
881    fn drawing_bottom(&self) -> Option<f32> {
882        self.drawing_infos
883            .iter()
884            .map(ItemDrawingInfo::bottom)
885            .max_by(f32::total_cmp)
886    }
887
888    /// Find the top-most of the currently visible items.
889    #[must_use]
890    /// Returns the index of the item currently at the top of the visible area.
891    pub fn get_top_item(&self) -> usize {
892        if self.drawing_infos.is_empty() {
893            return 0;
894        }
895        // drawing_infos contains content-space positions from the last draw.
896        // The visible top is at: first_element_y + scroll_offset
897        let first_element_y = self.drawing_top().unwrap();
898        let visible_top = first_element_y + self.scroll_offset;
899
900        self.drawing_infos
901            .iter()
902            .enumerate()
903            .find(|(_, di)| di.top() >= visible_top - 1.) // 1px margin for floating-point errors
904            .map_or(self.drawing_infos.len() - 1, |(idx, _)| idx)
905    }
906
907    //Return the y-coordinate of the first visible item in global coordinates
908    pub fn get_content_start(&self, ctx: &mut DrawingContext<'_>) -> f32 {
909        let first_element_top = self.drawing_top().unwrap();
910        let y = (ctx.to_screen)(0., 0.).y;
911        first_element_top - y
912    }
913
914    //Returns the y-coordinate of the current visible items in global coordinates
915    pub fn get_content_height(&self, ctx: &mut DrawingContext<'_>) -> f32 {
916        let last_element_bottom = self.drawing_bottom().unwrap();
917        let y = (ctx.to_screen)(0., 0.).y;
918        last_element_bottom - y
919    }
920
921    /// Find the item at a given y-location.
922    #[must_use]
923    pub fn get_item_at_y(&self, y: f32) -> Option<VisibleItemIndex> {
924        if self.drawing_infos.is_empty() {
925            return None;
926        }
927        let first_element_top = self.drawing_top().unwrap();
928        let first_element_bottom = self.drawing_bottom().unwrap();
929        let threshold = y + first_element_top + self.scroll_offset;
930        if first_element_bottom <= threshold {
931            return None;
932        }
933
934        self.drawing_infos
935            .iter()
936            .enumerate()
937            .rev()
938            .find(|(_, di)| di.top() <= threshold)
939            .map(|(vidx, _)| VisibleItemIndex(vidx))
940    }
941
942    pub fn scroll_to_item(&mut self, idx: usize) {
943        if self.drawing_infos.is_empty() {
944            return;
945        }
946        let first_element_y = self.drawing_top().unwrap();
947        let last_element_bottom = self.drawing_bottom().unwrap();
948        let content_height = last_element_bottom - first_element_y;
949
950        // Don't scroll if all content fits in viewport
951        let max_scroll = content_height - self.total_height;
952        if max_scroll <= 0.0 {
953            return;
954        }
955
956        let item_y = self
957            .drawing_infos
958            .get(idx)
959            .unwrap_or_else(|| self.drawing_infos.last().unwrap())
960            .top();
961        let target_scroll = item_y - first_element_y;
962
963        // Clamp scroll to valid range: [0, max_scroll]
964        self.scroll_offset = target_scroll.clamp(0.0, max_scroll);
965    }
966
967    /// Set cursor at next (or previous, if `next` is false) transition of `variable`. If `skip_zero` is true,
968    /// use the next transition to a non-zero value.
969    pub fn set_cursor_at_transition(
970        &mut self,
971        next: bool,
972        variable: Option<VisibleItemIndex>,
973        skip_zero: bool,
974    ) {
975        if let Some(vidx) = variable.or(self.focused_item)
976            && let Some(cursor) = &self.cursor
977            && let Some(DisplayedItem::Variable(variable)) = &self
978                .items_tree
979                .get_visible(vidx)
980                .and_then(|node| self.displayed_items.get(&node.item_ref))
981            && let Ok(Some(res)) = self.inner.as_waves().unwrap().query_variable(
982                &variable.variable_ref,
983                &cursor.to_biguint().unwrap_or_default(),
984            )
985        {
986            if next {
987                if let Some(ref time) = res.next {
988                    let stime = time.to_bigint();
989                    if stime.is_some() {
990                        self.cursor.clone_from(&stime);
991                    }
992                } else {
993                    // No next transition, go to end
994                    if let Some(end_time) = self.num_timestamps() {
995                        self.cursor = Some(end_time);
996                    } else {
997                        warn!(
998                            "Set cursor at transition: No timestamp count even though waveforms should be loaded"
999                        );
1000                    }
1001                }
1002            } else if let Some(stime) = res.current.unwrap().0.to_bigint() {
1003                let bigone = BigInt::from(1);
1004                // Check if we are on a transition
1005                if stime == *cursor && *cursor >= bigone {
1006                    // If so, subtract cursor position by one
1007                    if let Ok(Some(newres)) = self.inner.as_waves().unwrap().query_variable(
1008                        &variable.variable_ref,
1009                        &(cursor - bigone).to_biguint().unwrap_or_default(),
1010                    ) && let Some(current) = newres.current
1011                    {
1012                        let newstime = current.0.to_bigint();
1013                        if newstime.is_some() {
1014                            self.cursor.clone_from(&newstime);
1015                        }
1016                    }
1017                } else {
1018                    self.cursor = Some(stime);
1019                }
1020            }
1021
1022            // if zero edges should be skipped
1023            if skip_zero {
1024                // check if the next transition is 0, if so and requested, go to
1025                // next positive transition
1026                if let Some(time) = &self.cursor {
1027                    let next_value = self.inner.as_waves().unwrap().query_variable(
1028                        &variable.variable_ref,
1029                        &time.to_biguint().unwrap_or_default(),
1030                    );
1031                    if next_value.is_ok_and(|r| {
1032                        r.is_some_and(|r| {
1033                            r.current.is_some_and(|v| match v.1 {
1034                                VariableValue::BigUint(v) => v.is_zero(),
1035                                VariableValue::String(_) => false,
1036                            })
1037                        })
1038                    }) {
1039                        self.set_cursor_at_transition(next, Some(vidx), false);
1040                    }
1041                }
1042            }
1043        }
1044    }
1045
1046    pub fn next_displayed_item_ref(&mut self) -> DisplayedItemRef {
1047        self.display_item_ref_counter += 1;
1048        self.display_item_ref_counter.into()
1049    }
1050
1051    /// Returns the number of timestamps in the current waves. For now, this adjusts the
1052    /// number of timestamps as returned by wave sources if they specify 0 timestamps. This is
1053    /// done to avoid having to consider what happens with the viewport.
1054    #[must_use]
1055    pub fn num_timestamps(&self) -> Option<BigInt> {
1056        self.inner
1057            .max_timestamp()
1058            .and_then(|r| if r.is_zero() { None } else { Some(r) })
1059            .and_then(|r| r.to_bigint())
1060    }
1061
1062    /// Returns the number of timestamps in the current waves. This is like `num_timestamps` but
1063    /// will always return at least 1.
1064    #[must_use]
1065    pub fn safe_num_timestamps(&self) -> BigInt {
1066        self.num_timestamps().unwrap_or_else(BigInt::one)
1067    }
1068
1069    #[must_use]
1070    pub fn get_displayed_item_index(
1071        &self,
1072        item_ref: &DisplayedItemRef,
1073    ) -> Option<VisibleItemIndex> {
1074        // TODO check where this is called since it could now fail...
1075        self.items_tree
1076            .iter_visible()
1077            .enumerate()
1078            .find_map(|(vidx, node)| {
1079                if node.item_ref == *item_ref {
1080                    Some(VisibleItemIndex(vidx))
1081                } else {
1082                    None
1083                }
1084            })
1085    }
1086
1087    /// Spawn async worker to build analog cache. Worker holds Arc clone.
1088    pub fn build_analog_cache_async(
1089        &self,
1090        entry: std::sync::Arc<crate::analog_signal_cache::AnalogCacheEntry>,
1091        variable_ref: &VariableRef,
1092        translator: crate::translation::AnyTranslator,
1093        sender: &std::sync::mpsc::Sender<crate::message::Message>,
1094    ) -> Option<()> {
1095        let wave_container = self.inner.as_waves()?;
1096        let meta = wave_container.variable_meta(variable_ref).ok()?.clone();
1097
1098        let num_timestamps = self.num_timestamps()?.to_u64()?;
1099
1100        let accessor = wave_container.signal_accessor(entry.cache_key.0).ok()?;
1101
1102        let sender_clone = sender.clone();
1103        crate::async_util::perform_work(move || {
1104            let result = crate::analog_signal_cache::AnalogSignalCache::build(
1105                accessor,
1106                &translator,
1107                &meta,
1108                num_timestamps,
1109                None,
1110            );
1111
1112            let msg = match result {
1113                Some(cache) => crate::message::Message::AnalogCacheBuilt {
1114                    entry: entry.clone(),
1115                    result: Ok(cache),
1116                },
1117                None => crate::message::Message::AnalogCacheBuilt {
1118                    entry: entry.clone(),
1119                    result: Err("Failed to build analog cache".into()),
1120                },
1121            };
1122
1123            crate::OUTSTANDING_TRANSACTIONS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1124            let _ = sender_clone.send(msg);
1125
1126            if let Some(ctx) = crate::EGUI_CONTEXT.read().unwrap().as_ref() {
1127                ctx.request_repaint();
1128            }
1129        });
1130
1131        Some(())
1132    }
1133
1134    pub fn set_active_scope(&mut self, scope: Option<ScopeType>) -> Option<()> {
1135        if let Some(scope) = scope {
1136            let scope = if let ScopeType::StreamScope(StreamScopeRef::Empty(name)) = scope {
1137                let inner = self.inner.as_transactions()?;
1138                ScopeType::StreamScope(StreamScopeRef::new_stream_from_name(inner, name))
1139            } else {
1140                scope
1141            };
1142
1143            if self.inner.scope_exists(&scope) {
1144                self.active_scope = Some(scope);
1145            } else {
1146                warn!("Setting active scope to {scope} which does not exist");
1147            }
1148        } else {
1149            // Set to top-level scope
1150            self.active_scope = None;
1151        }
1152        Some(())
1153    }
1154}