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