Skip to main content

libsurfer/
wave_data.rs

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