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