Skip to main content

libsurfer/
lib.rs

1#![deny(unused_crate_dependencies)]
2
3pub mod analog_renderer;
4pub mod analog_signal_cache;
5pub mod annotation;
6pub mod annotation_list;
7pub mod arrow;
8pub mod async_util;
9pub mod batch_commands;
10#[cfg(feature = "performance_plot")]
11pub mod benchmark;
12mod channels;
13pub mod clock_highlighting;
14pub mod command_parser;
15pub mod command_prompt;
16pub mod comment;
17pub mod config;
18pub mod cxxrtl;
19pub mod cxxrtl_container;
20pub mod data_container;
21pub mod dialog;
22pub mod displayed_item;
23pub mod displayed_item_tree;
24pub mod drawing_canvas;
25pub mod file_dialog;
26pub mod file_history;
27pub mod file_watcher;
28pub mod frame_buffer;
29pub mod fzcmd;
30pub mod graphics;
31pub mod help;
32pub mod hierarchy;
33pub mod keyboard_shortcuts;
34pub mod keys;
35pub mod logs;
36pub mod marker;
37pub mod menus;
38pub mod message;
39pub mod mousegestures;
40pub mod overview;
41pub mod rectangle;
42pub mod remote;
43pub mod server_file_window;
44pub mod state;
45pub mod state_file_io;
46pub mod state_util;
47pub mod statusbar;
48pub mod system_state;
49#[cfg(test)]
50pub mod tests;
51pub mod time;
52pub mod toolbar;
53pub mod tooltips;
54pub mod transaction_container;
55pub mod transactions;
56pub mod translation;
57pub mod util;
58pub mod variable_direction;
59pub mod variable_filter;
60mod variable_index;
61pub mod variable_meta;
62pub mod variable_name_type;
63pub mod view;
64pub mod viewport;
65#[cfg(target_arch = "wasm32")]
66pub mod wasm_api;
67#[cfg(target_arch = "wasm32")]
68pub mod wasm_panic;
69pub mod wave_container;
70pub mod wave_data;
71pub mod wave_source;
72pub mod wcp;
73pub mod wellen;
74
75use crate::annotation::Annotatable;
76use crate::annotation::Annotation;
77use crate::annotation_list::AnnotationGroup;
78use crate::arrow::ArrowAnnotation;
79use crate::comment::CommentMessage;
80use crate::config::AutoLoad;
81use crate::displayed_item_tree::ItemIndex;
82use crate::displayed_item_tree::TargetPosition;
83use crate::rectangle::RectAnnotation;
84use crate::remote::get_time_table_from_server;
85use crate::variable_name_type::VariableNameType;
86
87use std::collections::HashMap;
88use std::sync::atomic::{AtomicUsize, Ordering};
89use std::sync::mpsc::{self, Receiver, Sender};
90use std::sync::{Arc, LazyLock, RwLock};
91
92use crate::channels::checked_send;
93use batch_commands::read_command_bytes;
94use batch_commands::read_command_file;
95#[cfg(target_arch = "wasm32")]
96use channels::{GlobalChannelTx, IngressHandler, IngressReceiver};
97use derive_more::Display;
98use displayed_item::DisplayedVariable;
99use displayed_item_tree::DisplayedItemTree;
100use eframe::{App, CreationContext};
101use egui::Id;
102use egui::{FontData, FontDefinitions, FontFamily};
103use eyre::{Result, WrapErr as _};
104use ftr_parser::types::Transaction;
105use futures::executor::block_on;
106use itertools::Itertools;
107use message::MessageTarget;
108use num::BigInt;
109use serde::Deserialize;
110use surfer_translation_types::Translator;
111use surfer_wcp::{WcpCSMessage, WcpEvent, WcpSCMessage};
112pub use system_state::SystemState;
113#[cfg(target_arch = "wasm32")]
114use tokio_stream as _;
115use tracing::{error, info, warn};
116#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
117use translation::wasm_translator::PluginTranslator;
118use wave_container::ScopeRef;
119
120#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
121use crate::async_util::perform_work;
122use crate::config::{SurferConfig, SurferTheme};
123use crate::dialog::{OpenSiblingStateFileDialog, ReloadWaveformDialog};
124use crate::displayed_item::{
125    AnalogVarState, DisplayedFieldRef, DisplayedItem, DisplayedItemRef, FieldFormat,
126};
127use crate::displayed_item_tree::VisibleItemIndex;
128use crate::drawing_canvas::TxDrawingCommands;
129use crate::frame_buffer::{FrameBufferColorMode, FrameBufferContent, build_frame_buffer_content};
130use crate::message::Message;
131use crate::transaction_container::{TransactionRef, TransactionStreamRef};
132use crate::translation::{AnyTranslator, all_translators};
133use crate::variable_filter::{VariableIOFilterType, VariableNameFilterType};
134use crate::viewport::Viewport;
135use crate::wave_container::{ScopeRefExt, VariableRefExt, WaveContainer};
136use crate::wave_data::WaveData;
137use crate::wave_source::{LoadOptions, WaveFormat, WaveSource};
138use crate::wellen::{HeaderResult, convert_format};
139
140/// A number that is non-zero if there are asynchronously triggered operations that
141/// have been triggered but not successfully completed yet. In practice, if this is
142/// non-zero, we will re-run the egui update function in order to ensure that we deal
143/// with the outstanding transactions eventually.
144/// When incrementing this, it is important to make sure that it gets decremented
145/// whenever the asynchronous transaction is completed, otherwise we will re-render
146/// things until program exit
147pub(crate) static OUTSTANDING_TRANSACTIONS: AtomicUsize = AtomicUsize::new(0);
148
149pub static EGUI_CONTEXT: LazyLock<RwLock<Option<Arc<egui::Context>>>> =
150    LazyLock::new(|| RwLock::new(None));
151
152#[cfg(target_arch = "wasm32")]
153pub(crate) static WCP_CS_HANDLER: LazyLock<IngressHandler<WcpCSMessage>> =
154    LazyLock::new(IngressHandler::new);
155
156#[cfg(target_arch = "wasm32")]
157pub(crate) static WCP_SC_HANDLER: LazyLock<GlobalChannelTx<WcpSCMessage>> =
158    LazyLock::new(GlobalChannelTx::new);
159
160#[derive(Default)]
161pub struct StartupParams {
162    pub waves: Option<WaveSource>,
163    pub wcp_initiate: Option<u16>,
164    pub startup_commands: Vec<String>,
165}
166
167fn setup_custom_font(ctx: &egui::Context) {
168    let mut fonts = FontDefinitions::default();
169
170    fonts.font_data.insert(
171        "remix_icons".to_owned(),
172        FontData::from_static(egui_remixicon::FONT).into(),
173    );
174
175    fonts
176        .families
177        .get_mut(&FontFamily::Proportional)
178        .unwrap()
179        .push("remix_icons".to_owned());
180
181    fonts
182        .families
183        .get_mut(&FontFamily::Monospace)
184        .unwrap()
185        .push("remix_icons".to_owned());
186
187    ctx.set_fonts(fonts);
188}
189
190pub fn run_egui(cc: &CreationContext, mut state: SystemState) -> Result<Box<dyn App>> {
191    let ctx_arc = Arc::new(cc.egui_ctx.clone());
192    *EGUI_CONTEXT.write().unwrap() = Some(ctx_arc.clone());
193    state.context = Some(ctx_arc.clone());
194    cc.egui_ctx
195        .set_visuals_of(egui::Theme::Dark, state.get_visuals());
196    cc.egui_ctx
197        .set_visuals_of(egui::Theme::Light, state.get_visuals());
198    cc.egui_ctx.all_styles_mut(|style| {
199        if state.user.config.animation_time == 0.0 {
200            info!("With animation_time set to 0.0, animations cannot be enabled.");
201        }
202        style.animation_time = if state.user.config.animation_enabled() {
203            state.user.config.animation_time
204        } else {
205            0.0
206        };
207    });
208    #[cfg(not(target_arch = "wasm32"))]
209    if state.user.config.wcp.autostart {
210        state.start_wcp_server(Some(state.user.config.wcp.address.clone()), false);
211    }
212    setup_custom_font(&cc.egui_ctx);
213    Ok(Box::new(state))
214}
215
216#[derive(Debug, Clone, Copy, Deserialize, Display, PartialEq, Eq)]
217pub enum MoveDir {
218    #[display("up")]
219    Up,
220
221    #[display("down")]
222    Down,
223}
224
225pub enum ColorSpecifier {
226    Index(usize),
227    Name(String),
228}
229
230enum CachedDrawData {
231    WaveDrawData(CachedWaveDrawData),
232    TransactionDrawData(CachedTransactionDrawData),
233}
234
235struct CachedWaveDrawData {
236    pub draw_commands: HashMap<DisplayedFieldRef, drawing_canvas::DrawingCommands>,
237    pub clock_edges: crate::clock_highlighting::ClockHighlightData,
238    pub ticks: Vec<(String, f32, i64)>,
239}
240
241struct CachedTransactionDrawData {
242    pub draw_commands: HashMap<TransactionRef, TxDrawingCommands>,
243    pub stream_to_displayed_txs: HashMap<TransactionStreamRef, Vec<TransactionRef>>,
244    pub inc_relation_tx_ids: Vec<TransactionRef>,
245    pub out_relation_tx_ids: Vec<TransactionRef>,
246}
247
248pub struct Channels {
249    pub msg_sender: Sender<Message>,
250    pub msg_receiver: Receiver<Message>,
251    #[cfg(target_arch = "wasm32")]
252    wcp_c2s_receiver: Option<IngressReceiver<WcpCSMessage>>,
253    #[cfg(not(target_arch = "wasm32"))]
254    wcp_c2s_receiver: Option<tokio::sync::mpsc::Receiver<WcpCSMessage>>,
255    wcp_s2c_sender: Option<tokio::sync::mpsc::Sender<WcpSCMessage>>,
256}
257impl Channels {
258    fn new() -> Self {
259        let (msg_sender, msg_receiver) = mpsc::channel();
260        Self {
261            msg_sender,
262            msg_receiver,
263            wcp_c2s_receiver: None,
264            wcp_s2c_sender: None,
265        }
266    }
267}
268
269pub struct WcpClientCapabilities {
270    pub waveforms_loaded: bool,
271    pub goto_declaration: bool,
272    pub add_drivers: bool,
273    pub add_loads: bool,
274}
275impl WcpClientCapabilities {
276    fn new() -> Self {
277        Self {
278            waveforms_loaded: false,
279            goto_declaration: false,
280            add_drivers: false,
281            add_loads: false,
282        }
283    }
284}
285
286/// Stores the current canvas state to enable undo/redo operations
287struct CanvasState {
288    message: String,
289    focused_item: Option<VisibleItemIndex>,
290    focused_transaction: (Option<TransactionRef>, Option<Transaction>),
291    items_tree: DisplayedItemTree,
292    displayed_items: HashMap<DisplayedItemRef, DisplayedItem>,
293    markers: HashMap<u8, BigInt>,
294    annotations: Vec<Annotation>,
295    annotation_group: Vec<AnnotationGroup>,
296    annotation_list: bool,
297    selected_annotation: Option<Id>,
298    annotation_counter: i32,
299}
300
301impl SystemState {
302    pub fn update(&mut self, message: Message) -> Option<()> {
303        if tracing::enabled!(tracing::Level::TRACE)
304            && !matches!(message, Message::CommandPromptUpdate { .. })
305        {
306            tracing::trace!("{message:?}");
307        }
308        match message {
309            Message::SetActiveScope(scope) => {
310                let waves = self.user.waves.as_mut()?;
311                waves.set_active_scope(scope)?;
312            }
313
314            Message::ExpandScope(scope_ref) => {
315                *self.scope_ref_to_expand.borrow_mut() = Some(scope_ref);
316            }
317            Message::AddVariables(vars) => {
318                if !vars.is_empty() {
319                    let undo_msg = if vars.len() == 1 {
320                        format!("Add variable {}", vars[0].name)
321                    } else {
322                        format!("Add {} variables", vars.len())
323                    };
324                    self.save_current_canvas(undo_msg);
325                    if let Some(waves) = self.user.waves.as_mut() {
326                        if let (Some(cmd), _) =
327                            waves.add_variables(&self.translators, vars, None, true, false, None)
328                        {
329                            self.load_variables(cmd);
330                        }
331                        self.invalidate_draw_commands();
332                    } else {
333                        error!("Could not load signals, no waveform loaded");
334                    }
335                }
336            }
337            Message::DownloadDefaultConfig => {
338                #[cfg(not(target_arch = "wasm32"))]
339                {
340                    if let Err(e) = crate::config::write_default_config() {
341                        tracing::error!("Failed to write default config: {}", e);
342                    }
343                }
344
345                #[cfg(target_arch = "wasm32")]
346                {
347                    tracing::warn!("Download default config is not supported on WASM");
348                }
349            }
350
351            Message::AddDivider(name, vidx) => {
352                self.save_current_canvas("Add divider".into());
353                let waves = self.user.waves.as_mut()?;
354                waves.add_divider(name, vidx);
355            }
356            Message::AddTimeLine(vidx) => {
357                self.save_current_canvas("Add timeline".into());
358                let waves = self.user.waves.as_mut()?;
359                waves.add_timeline(vidx);
360            }
361            Message::AddScope(scope, recursive) => {
362                self.save_current_canvas(format!("Add scope {}", scope.name()));
363
364                let vars = self.get_scope(scope, recursive);
365                let waves = self.user.waves.as_mut()?;
366
367                // TODO add parameter to add_variables, insert to (self.drag_target_idx, self.drag_source_idx)
368                if let (Some(cmd), _) =
369                    waves.add_variables(&self.translators, vars, None, true, false, None)
370                {
371                    self.load_variables(cmd);
372                }
373
374                self.invalidate_draw_commands();
375            }
376            Message::AddScopeAsGroup(scope, recursive) => {
377                self.save_current_canvas(format!("Add scope {} as group", scope.name()));
378                let waves = self.user.waves.as_mut()?;
379                let passed_or_focused = waves.insert_position(waves.focused_item);
380                let target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
381
382                self.add_scope_as_group(&scope, target, recursive, None);
383                self.invalidate_draw_commands();
384
385                self.user.waves.as_mut()?.compute_variable_display_names();
386            }
387            Message::AddCount(digit) => {
388                if let Some(count) = &mut self.user.count {
389                    count.push(digit);
390                } else {
391                    self.user.count = Some(digit.to_string());
392                }
393            }
394            Message::AddStreamOrGenerator(s) => {
395                let undo_msg = if let Some(gen_id) = s.gen_id {
396                    format!("Add generator(id: {gen_id})")
397                } else {
398                    format!("Add stream(id: {})", s.stream_id)
399                };
400                self.save_current_canvas(undo_msg);
401
402                let waves = self.user.waves.as_mut()?;
403                if s.gen_id.is_some() {
404                    waves.add_generator(s);
405                } else {
406                    waves.add_stream(s);
407                }
408                self.invalidate_draw_commands();
409            }
410            Message::AddStreamOrGeneratorFromName(scope, name) => {
411                self.save_current_canvas(format!("Add Stream/Generator from name: {}", &name));
412                let waves = self.user.waves.as_mut()?;
413                waves.add_stream_or_generator_from_name(scope, name)?;
414                self.invalidate_draw_commands();
415            }
416            Message::AddAllFromStreamScope(scope_name) => {
417                self.save_current_canvas(format!("Add all from scope {}", scope_name.clone()));
418                let waves = self.user.waves.as_mut()?;
419                waves.add_all_from_stream_scope(scope_name)?;
420                self.invalidate_draw_commands();
421            }
422            Message::InvalidateCount => self.user.count = None,
423            Message::SetNameAlignRight(align_right) => {
424                self.user.align_names_right = Some(align_right);
425            }
426            Message::FocusItem(idx) => {
427                let waves = self.user.waves.as_mut()?;
428
429                let visible_items_len = waves.displayed_items.len();
430                if idx.0 < visible_items_len {
431                    waves.focused_item = Some(idx);
432                } else {
433                    error!(
434                        "Can not focus variable {} because only {visible_items_len} variables are visible.",
435                        idx.0
436                    );
437                }
438            }
439            Message::ItemSelectRange(select_to) => {
440                let waves = self.user.waves.as_mut()?;
441                let select_from = waves.focused_item?;
442                waves
443                    .items_tree
444                    .xselect_visible_range(select_from, select_to, true);
445            }
446            Message::ItemSelectAll => {
447                let waves = self.user.waves.as_mut()?;
448                waves.items_tree.xselect_all_visible(true);
449            }
450            Message::SetItemSelected(vidx, selected) => {
451                let waves = self.user.waves.as_mut()?;
452                waves.items_tree.xselect(vidx, selected);
453            }
454            Message::ToggleItemSelected(vidx) => {
455                let waves = self.user.waves.as_mut()?;
456                let node = vidx
457                    .or(waves.focused_item)
458                    .and_then(|vidx| waves.items_tree.to_displayed(vidx))
459                    .and_then(|item| waves.items_tree.get_mut(item))?;
460                node.selected = !node.selected;
461            }
462            Message::SetDefaultTimeline(v) => {
463                self.user.show_default_timeline = Some(v);
464            }
465            Message::UnfocusItem => {
466                let waves = self.user.waves.as_mut()?;
467                waves.focused_item = None;
468            }
469            Message::MoveFocus(direction, count, select) => {
470                let waves = self.user.waves.as_mut()?;
471                let visible_item_cnt = waves.items_tree.iter_visible().count();
472                if visible_item_cnt == 0 {
473                    return None;
474                }
475
476                let new_focus_vidx = VisibleItemIndex(match direction {
477                    MoveDir::Up => waves
478                        .focused_item
479                        .map_or(visible_item_cnt, |vidx| vidx.0)
480                        .saturating_sub(count),
481                    MoveDir::Down => waves
482                        .focused_item
483                        .map_or(usize::MAX, |vidx| vidx.0)
484                        .wrapping_add(count)
485                        .clamp(0, visible_item_cnt - 1),
486                });
487
488                if select {
489                    if let Some(vidx) = waves.focused_item {
490                        waves.items_tree.xselect(vidx, true);
491                    }
492                    waves.items_tree.xselect(new_focus_vidx, true);
493                }
494                waves.focused_item = Some(new_focus_vidx);
495            }
496            Message::FocusTransaction(tx_ref, tx) => {
497                if let Some(tx_ref) = tx_ref.as_ref()
498                    && tx.is_none()
499                {
500                    self.save_current_canvas(format!("Focus Transaction id: {}", tx_ref.id));
501                }
502                let waves = self.user.waves.as_mut()?;
503                let invalidate = tx.is_none();
504                waves.focused_transaction =
505                    (tx_ref, tx.or_else(|| waves.focused_transaction.1.clone()));
506                if invalidate {
507                    self.invalidate_draw_commands();
508                }
509            }
510            Message::ScrollToItem(position) => {
511                let waves = self.user.waves.as_mut()?;
512                waves.scroll_to_item(position);
513            }
514            Message::SetScrollOffset(offset) => {
515                let waves = self.user.waves.as_mut()?;
516                waves.scroll_offset = offset;
517            }
518            Message::SetLogsVisible(visibility) => self.user.show_logs = visibility,
519            Message::SetFrameBufferVariable(variable_ref) => {
520                let waves = self.user.waves.as_mut()?;
521                if let Some(cmd) = waves
522                    .inner
523                    .as_waves_mut()?
524                    .load_variables(std::iter::once(&variable_ref))
525                    .map_err(|e| error!("{e:#?}"))
526                    .ok()
527                    .flatten()
528                {
529                    self.load_variables(cmd);
530                }
531                self.frame_buffer_content = Some(FrameBufferContent::Variable(variable_ref));
532            }
533            Message::SetFrameBufferVisibleVariable(None) => {
534                self.frame_buffer_content = None;
535            }
536            Message::SetFrameBufferVisibleVariable(Some(vidx)) => {
537                let waves = self.user.waves.as_ref()?;
538                self.frame_buffer_content = waves
539                    .items_tree
540                    .get_visible(vidx)
541                    .and_then(|node| waves.displayed_items.get(&node.item_ref))
542                    .and_then(|item| match item {
543                        DisplayedItem::Variable(variable) => {
544                            Some(FrameBufferContent::Variable(variable.variable_ref.clone()))
545                        }
546                        _ => None,
547                    });
548            }
549            Message::SetFrameBufferArray(scope_ref) => {
550                let waves = self.user.waves.as_mut()?;
551                let (levels, all_leaf_vars) = {
552                    let wave_container = waves.inner.as_waves()?;
553                    build_frame_buffer_content(wave_container, &scope_ref)?
554                };
555                if let Some(cmd) = waves
556                    .inner
557                    .as_waves_mut()?
558                    .load_variables(all_leaf_vars.iter())
559                    .map_err(|e| error!("{e:#?}"))
560                    .ok()
561                    .flatten()
562                {
563                    self.load_variables(cmd);
564                }
565                self.frame_buffer_content = Some(FrameBufferContent::Array { scope_ref, levels });
566            }
567            Message::SetFrameBufferMode(mode, bits1, bits2, bits3) => {
568                let settings = &mut self.user.frame_buffer.color_settings;
569                settings.color_mode = mode;
570                match mode {
571                    FrameBufferColorMode::Grayscale => {
572                        settings.grayscale_bits = bits1.clamp(1, 8);
573                    }
574                    FrameBufferColorMode::Rgb => {
575                        settings.r_bits = bits1.min(8);
576                        settings.g_bits = bits2.min(8);
577                        settings.b_bits = bits3.min(8);
578                    }
579                    FrameBufferColorMode::YCbCr => {
580                        settings.y_bits = bits1.min(8);
581                        settings.cb_bits = bits2.min(8);
582                        settings.cr_bits = bits3.min(8);
583                    }
584                }
585                self.frame_buffer_pixel_cache = None;
586            }
587            Message::SetFrameBufferWidth(width) => {
588                self.user.frame_buffer.pixels_per_row = width.max(1);
589            }
590            Message::SetFrameBufferRange(ranges) => {
591                let Some(FrameBufferContent::Array { levels, .. }) =
592                    self.frame_buffer_content.as_mut()
593                else {
594                    return None;
595                };
596
597                for (level, (first, last)) in levels.iter_mut().zip(ranges) {
598                    let mut clamped_first = first.clamp(level.min_index, level.max_index);
599                    let mut clamped_last = last.clamp(level.min_index, level.max_index);
600                    if clamped_first > clamped_last {
601                        std::mem::swap(&mut clamped_first, &mut clamped_last);
602                    }
603                    level.first_index = clamped_first;
604                    level.last_index = clamped_last;
605                }
606            }
607            Message::SetCursorWindowVisible(visibility) => {
608                self.user.show_cursor_window = visibility;
609            }
610            Message::VerticalScroll(direction, count) => {
611                let waves = self.user.waves.as_mut()?;
612                let current_item = waves.get_top_item();
613                match direction {
614                    MoveDir::Down => {
615                        waves.scroll_to_item(current_item + count);
616                    }
617                    MoveDir::Up => {
618                        if current_item > count {
619                            waves.scroll_to_item(current_item - count);
620                        } else {
621                            waves.scroll_to_item(0);
622                        }
623                    }
624                }
625            }
626            Message::SetSurverFileWindowVisible(visibility) => {
627                self.user.show_server_file_window = visibility;
628            }
629            Message::LoadSurverFileByIndex(file_index, load_options) => {
630                // Disable file window in case executing from command/test
631                self.user.show_server_file_window = false;
632                let force_switch = self.user.selected_server_file_index != file_index;
633                if let Some(url) = self.user.surver_url.as_ref() {
634                    self.load_wave_from_url(url.clone(), load_options, force_switch, file_index);
635                }
636            }
637            Message::LoadSurverFileByName(file_name, load_options) => {
638                // Disable file window in case executing from command/test
639                self.user.show_server_file_window = false;
640                let file_index = self
641                    .user
642                    .surver_file_infos
643                    .as_ref()?
644                    .iter()
645                    .position(|fi| fi.filename == file_name);
646                let force_switch = self.user.selected_server_file_index != file_index;
647
648                if let Some(url) = self.user.surver_url.as_ref() {
649                    self.load_wave_from_url(url.clone(), load_options, force_switch, file_index);
650                }
651            }
652            Message::RemoveVisibleItems(target) => match target {
653                MessageTarget::Explicit(vidx) => {
654                    let waves = self.user.waves.as_ref();
655                    let item_ref = waves
656                        .and_then(|waves| waves.items_tree.get_visible(vidx))
657                        .map(|node| node.item_ref);
658                    let undo_msg = item_ref
659                        .and_then(|item_ref| {
660                            waves.and_then(|waves| waves.displayed_items.get(&item_ref))
661                        })
662                        .map(displayed_item::DisplayedItem::name)
663                        .map_or("Remove one item".to_string(), |name| {
664                            format!("Remove item {name}")
665                        });
666                    self.save_current_canvas(undo_msg);
667
668                    if let Some(waves) = self.user.waves.as_mut()
669                        && let Some(item_ref) = item_ref
670                    {
671                        waves.remove_displayed_item(item_ref);
672                        waves.compute_variable_display_names();
673                    }
674                }
675                MessageTarget::CurrentSelection => {
676                    self.save_current_canvas("Remove selected items".to_owned());
677                    let waves = self.user.waves.as_mut()?;
678
679                    let mut remove_ids: Vec<_> = waves
680                        .items_tree
681                        .iter_visible_selected()
682                        .map(|node| node.item_ref)
683                        .collect();
684                    if let Some(node) = waves
685                        .focused_item
686                        .and_then(|focus| waves.items_tree.get_visible(focus))
687                    {
688                        remove_ids.push(node.item_ref);
689                    }
690                    for &item_ref in &remove_ids {
691                        waves.remove_displayed_item(item_ref);
692                    }
693                    waves.compute_variable_display_names();
694                }
695            },
696            Message::RemoveItems(items) => {
697                let undo_msg = self
698                    .user
699                    .waves
700                    .as_ref()
701                    .and_then(|waves| {
702                        if items.len() == 1 {
703                            items.first().and_then(|item_ref| {
704                                waves
705                                    .displayed_items
706                                    .get(item_ref)
707                                    .map(|item| format!("Remove item {}", item.name()))
708                            })
709                        } else {
710                            Some(format!("Remove {} items", items.len()))
711                        }
712                    })
713                    .unwrap_or_default();
714                self.save_current_canvas(undo_msg);
715
716                let waves = self.user.waves.as_mut()?;
717                for id in items.iter().sorted_unstable_by(|a, b| Ord::cmp(b, a)) {
718                    waves.remove_displayed_item(*id);
719                }
720            }
721            Message::MoveFocusedItem(direction, count) => {
722                self.save_current_canvas(format!("Move item {direction}, {count}"));
723                self.invalidate_draw_commands();
724                let waves = self.user.waves.as_mut()?;
725                let mut vidx = waves.focused_item?;
726                for _ in 0..count {
727                    vidx = waves
728                        .items_tree
729                        .move_item(vidx, direction, |node| {
730                            matches!(
731                                waves.displayed_items.get(&node.item_ref),
732                                Some(DisplayedItem::Group(..))
733                            )
734                        })
735                        .expect("move failed for unknown reason");
736                }
737                waves.focused_item = waves.focused_item.and(Some(vidx));
738            }
739            Message::CanvasScroll {
740                delta,
741                viewport_idx,
742            } => {
743                let waves = self.user.waves.as_mut()?;
744                waves.viewports[viewport_idx]
745                    .handle_canvas_scroll(f64::from(delta.y) + f64::from(delta.x));
746                self.invalidate_draw_commands();
747            }
748            Message::CanvasZoom {
749                delta,
750                mouse_ptr,
751                viewport_idx,
752            } => {
753                let waves = self.user.waves.as_mut()?;
754                if let Some(num_timestamps) = waves.num_timestamps() {
755                    waves.viewports[viewport_idx].handle_canvas_zoom(
756                        mouse_ptr,
757                        f64::from(delta),
758                        &num_timestamps,
759                    );
760                    self.invalidate_draw_commands();
761                } else {
762                    warn!(
763                        "Canvas zoom: No timestamps count, even though waveforms should be loaded"
764                    );
765                }
766            }
767            Message::ZoomToFit { viewport_idx } => {
768                let waves = self.user.waves.as_mut()?;
769                waves.viewports[viewport_idx].zoom_to_fit();
770                self.invalidate_draw_commands();
771            }
772            Message::GoToEnd { viewport_idx } => {
773                let waves = self.user.waves.as_mut()?;
774                waves.viewports[viewport_idx].go_to_end();
775                self.invalidate_draw_commands();
776            }
777            Message::GoToStart { viewport_idx } => {
778                let waves = self.user.waves.as_mut()?;
779                waves.viewports[viewport_idx].go_to_start();
780                self.invalidate_draw_commands();
781            }
782            Message::GoToTime(time, viewport_idx) => {
783                let waves = self.user.waves.as_mut()?;
784                // If there are no timestamps, the file is not fully loaded
785                if let Some(num_timestamps) = waves.num_timestamps() {
786                    let time = time?;
787                    waves.viewports[viewport_idx].go_to_time(&time.clone(), &num_timestamps);
788                    self.invalidate_draw_commands();
789                } else {
790                    warn!(
791                        "Go to time: No timestamps count, even though waveforms should be loaded"
792                    );
793                }
794            }
795            Message::SetTimeUnit(timeunit) => {
796                self.user.wanted_timeunit = timeunit;
797                self.invalidate_draw_commands();
798            }
799            Message::SetTimeStringFormatting(format) => {
800                self.user.time_string_format = format;
801                self.invalidate_draw_commands();
802            }
803            Message::ZoomToRange {
804                start,
805                end,
806                viewport_idx,
807            } => {
808                let waves = self.user.waves.as_mut()?;
809                // If there are no timestamps, the file is not fully loaded
810                if let Some(num_timestamps) = waves.num_timestamps() {
811                    waves.viewports[viewport_idx].zoom_to_range(&start, &end, &num_timestamps);
812                    self.invalidate_draw_commands();
813                } else {
814                    warn!(
815                        "Zoom to range: No timestamps count, even though waveforms should be loaded"
816                    );
817                }
818            }
819            Message::VariableFormatChange(displayed_field_ref, format) => {
820                let waves = self.user.waves.as_mut()?;
821                if !self
822                    .translators
823                    .all_translator_names()
824                    .contains(&format.as_str())
825                {
826                    warn!("No translator {format}");
827                    return None;
828                }
829
830                let update_format =
831                    |variable: &mut DisplayedVariable, field_ref: DisplayedFieldRef| {
832                        if field_ref.field.is_empty() {
833                            let Ok(meta) = waves
834                                .inner
835                                .as_waves()
836                                .unwrap()
837                                .variable_meta(&variable.variable_ref)
838                                .map_err(|e| {
839                                    warn!("Error trying to get variable metadata: {e:#?}");
840                                })
841                            else {
842                                return;
843                            };
844                            let translator = self.translators.get_translator(&format);
845                            let new_info = translator.variable_info(&meta).unwrap();
846
847                            variable.format = Some(format.clone());
848                            variable.info = new_info;
849
850                            variable.downgrade_type_limits_if_unsupported(translator, &meta);
851                        } else {
852                            variable
853                                .field_formats
854                                .retain(|ff| ff.field != field_ref.field);
855                            variable.field_formats.push(FieldFormat {
856                                field: field_ref.field,
857                                format: format.clone(),
858                            });
859                        }
860                    };
861
862                // convert focused item index to item ref
863                let focused = waves
864                    .focused_item
865                    .and_then(|vidx| waves.items_tree.get_visible(vidx))
866                    .map(|node| node.item_ref);
867
868                let mut redraw = false;
869
870                match displayed_field_ref {
871                    MessageTarget::Explicit(field_ref) => {
872                        if let Some(DisplayedItem::Variable(displayed_variable)) =
873                            waves.displayed_items.get_mut(&field_ref.item)
874                        {
875                            update_format(displayed_variable, field_ref);
876                            redraw = true;
877                        }
878                    }
879                    MessageTarget::CurrentSelection => {
880                        //If an item is focused, update its format too
881                        if let Some(focused) = focused
882                            && let Some(DisplayedItem::Variable(displayed_variable)) =
883                                waves.displayed_items.get_mut(&focused)
884                        {
885                            update_format(displayed_variable, DisplayedFieldRef::from(focused));
886                            redraw = true;
887                        }
888                        for item in waves
889                            .items_tree
890                            .iter_visible_selected()
891                            .map(|node| node.item_ref)
892                        {
893                            //Update format for all selected
894                            let field_ref = DisplayedFieldRef::from(item);
895                            if let Some(DisplayedItem::Variable(variable)) =
896                                waves.displayed_items.get_mut(&item)
897                            {
898                                update_format(variable, field_ref);
899                            }
900                            redraw = true;
901                        }
902                    }
903                }
904
905                if redraw {
906                    self.invalidate_draw_commands();
907                }
908            }
909            Message::ItemSelectionClear => {
910                let waves = self.user.waves.as_mut()?;
911                waves.items_tree.xselect_all_visible(false);
912            }
913            Message::ItemColorChange(vidx, color_name) => {
914                self.save_current_canvas(format!(
915                    "Change item color to {}",
916                    color_name.clone().unwrap_or("default".into())
917                ));
918                self.invalidate_draw_commands();
919                let waves = self.user.waves.as_mut()?;
920
921                match vidx {
922                    MessageTarget::Explicit(vidx) => {
923                        let node = waves.items_tree.get_visible(vidx)?;
924                        waves
925                            .displayed_items
926                            .entry(node.item_ref)
927                            .and_modify(|item| item.set_color(&color_name));
928                    }
929                    MessageTarget::CurrentSelection => {
930                        if let Some(focused) = waves.focused_item {
931                            let node = waves.items_tree.get_visible(focused)?;
932                            waves
933                                .displayed_items
934                                .entry(node.item_ref)
935                                .and_modify(|item| item.set_color(&color_name));
936                        }
937
938                        for node in waves.items_tree.iter_visible_selected() {
939                            waves
940                                .displayed_items
941                                .entry(node.item_ref)
942                                .and_modify(|item| item.set_color(&color_name));
943                        }
944                    }
945                }
946            }
947            Message::ItemNameChange(vidx, name) => {
948                self.save_current_canvas(format!(
949                    "Change item name to {}",
950                    name.clone().unwrap_or("default".into())
951                ));
952                let waves = self.user.waves.as_mut()?;
953                let vidx = vidx.or(waves.focused_item)?;
954                let node = waves.items_tree.get_visible(vidx)?;
955                waves
956                    .displayed_items
957                    .entry(node.item_ref)
958                    .and_modify(|item| item.set_name(name));
959            }
960            Message::ItemNameReset(target) => {
961                self.save_current_canvas("Resetting item name(s)".to_owned());
962                let waves = self.user.waves.as_mut()?;
963                match target {
964                    MessageTarget::Explicit(vidx) => {
965                        let node = waves.items_tree.get_visible(vidx)?;
966                        waves
967                            .displayed_items
968                            .entry(node.item_ref)
969                            .and_modify(|item| item.set_name(None));
970                    }
971                    MessageTarget::CurrentSelection => {
972                        for node in waves.items_tree.iter_visible_selected() {
973                            waves
974                                .displayed_items
975                                .entry(node.item_ref)
976                                .and_modify(|item| item.set_name(None));
977                        }
978                    }
979                }
980            }
981            Message::ItemBackgroundColorChange(vidx, color_name) => {
982                self.save_current_canvas(format!(
983                    "Change item background color to {}",
984                    color_name.clone().unwrap_or("default".into())
985                ));
986                let waves = self.user.waves.as_mut()?;
987
988                match vidx {
989                    MessageTarget::Explicit(vidx) => {
990                        let node = waves.items_tree.get_visible(vidx)?;
991                        waves
992                            .displayed_items
993                            .entry(node.item_ref)
994                            .and_modify(|item| item.set_background_color(&color_name));
995                    }
996                    MessageTarget::CurrentSelection => {
997                        if let Some(focused) = waves.focused_item {
998                            let node = waves.items_tree.get_visible(focused)?;
999                            waves
1000                                .displayed_items
1001                                .entry(node.item_ref)
1002                                .and_modify(|item| item.set_background_color(&color_name));
1003                        }
1004
1005                        for node in waves.items_tree.iter_visible_selected() {
1006                            waves
1007                                .displayed_items
1008                                .entry(node.item_ref)
1009                                .and_modify(|item| item.set_background_color(&color_name));
1010                        }
1011                    }
1012                }
1013            }
1014            Message::ItemHeightScalingFactorChange(vidx, scale) => {
1015                self.save_current_canvas(format!("Change item height scaling factor to {scale}"));
1016                let waves = self.user.waves.as_mut()?;
1017
1018                match vidx {
1019                    MessageTarget::Explicit(vidx) => {
1020                        let node = waves.items_tree.get_visible(vidx)?;
1021                        waves
1022                            .displayed_items
1023                            .entry(node.item_ref)
1024                            .and_modify(|item| item.set_height_scaling_factor(scale));
1025                    }
1026                    MessageTarget::CurrentSelection => {
1027                        if let Some(focused) = waves.focused_item {
1028                            let node = waves.items_tree.get_visible(focused)?;
1029                            waves
1030                                .displayed_items
1031                                .entry(node.item_ref)
1032                                .and_modify(|item| item.set_height_scaling_factor(scale));
1033                        }
1034
1035                        for node in waves.items_tree.iter_visible_selected() {
1036                            waves
1037                                .displayed_items
1038                                .entry(node.item_ref)
1039                                .and_modify(|item| item.set_height_scaling_factor(scale));
1040                        }
1041                    }
1042                }
1043            }
1044            Message::SetAnalogSettings(vidx, new_settings) => {
1045                self.save_current_canvas("Set analog state".into());
1046                self.invalidate_draw_commands();
1047                let analog_waveform_multiplier = self.user.config.layout.analog_waveform_multiplier;
1048                let waves = self.user.waves.as_mut()?;
1049
1050                // Update settings while preserving existing cache
1051                let update = |item: &mut DisplayedItem| {
1052                    if let DisplayedItem::Variable(var) = item {
1053                        match (&mut var.analog, new_settings) {
1054                            (Some(s), Some(new)) => s.settings = new,
1055                            (None, Some(new)) => {
1056                                var.analog = Some(AnalogVarState::new(new));
1057                                var.height_scaling_factor = Some(analog_waveform_multiplier);
1058                            }
1059                            (_, None) => var.analog = None,
1060                        }
1061                    }
1062                };
1063
1064                match vidx {
1065                    MessageTarget::Explicit(vidx) => {
1066                        let node = waves.items_tree.get_visible(vidx)?;
1067                        waves
1068                            .displayed_items
1069                            .entry(node.item_ref)
1070                            .and_modify(update);
1071                    }
1072                    MessageTarget::CurrentSelection => {
1073                        if let Some(focused) = waves.focused_item {
1074                            let node = waves.items_tree.get_visible(focused)?;
1075                            waves
1076                                .displayed_items
1077                                .entry(node.item_ref)
1078                                .and_modify(update);
1079                        }
1080                        for node in waves.items_tree.iter_visible_selected() {
1081                            waves
1082                                .displayed_items
1083                                .entry(node.item_ref)
1084                                .and_modify(update);
1085                        }
1086                    }
1087                }
1088            }
1089            Message::MoveCursorToTransition {
1090                next,
1091                variable,
1092                skip_zero,
1093            } => {
1094                let waves = self.user.waves.as_mut()?;
1095                // If there are no timestamps, the file is not fully loaded
1096                if let Some(num_timestamps) = waves.num_timestamps() {
1097                    // if no cursor is set, move it to
1098                    // start of visible area transition for next transition
1099                    // end of visible area for previous transition
1100                    if waves.cursor.is_none()
1101                        && waves.focused_item.is_some()
1102                        && let Some(vp) = waves.viewports.first()
1103                    {
1104                        waves.cursor = if next {
1105                            Some(vp.left_edge_time(&num_timestamps))
1106                        } else {
1107                            Some(vp.right_edge_time(&num_timestamps))
1108                        };
1109                    }
1110                    waves.set_cursor_at_transition(next, variable, skip_zero);
1111                    let moved = waves.go_to_cursor_if_not_in_view();
1112                    if moved {
1113                        self.invalidate_draw_commands();
1114                    }
1115                } else {
1116                    warn!(
1117                        "Move cursor to transition: No timestamps count, even though waveforms should be loaded"
1118                    );
1119                }
1120            }
1121            Message::MoveTransaction { next } => {
1122                let undo_msg = if next {
1123                    "Move to next transaction"
1124                } else {
1125                    "Move to previous transaction"
1126                };
1127                self.save_current_canvas(undo_msg.to_string());
1128                let waves = self.user.waves.as_mut()?;
1129                waves.move_to_transaction(next)?;
1130                self.invalidate_draw_commands();
1131            }
1132            Message::ResetVariableFormat(displayed_field_ref) => {
1133                let waves = self.user.waves.as_mut()?;
1134                if let Some(DisplayedItem::Variable(displayed_variable)) =
1135                    waves.displayed_items.get_mut(&displayed_field_ref.item)
1136                {
1137                    if displayed_field_ref.field.is_empty() {
1138                        displayed_variable.format = None;
1139                    } else {
1140                        displayed_variable
1141                            .field_formats
1142                            .retain(|ff| ff.field != displayed_field_ref.field);
1143                    }
1144                    self.invalidate_draw_commands();
1145                }
1146            }
1147            Message::CursorSet(time) => {
1148                let waves = self.user.waves.as_mut()?;
1149                waves.cursor = Some(time);
1150            }
1151            Message::ExpandParameterSection => {
1152                self.expand_parameter_section = true;
1153            }
1154            Message::LoadFile(filename, load_options) => {
1155                self.user.selected_server_file_index = None;
1156                *self.surver_selected_file.borrow_mut() = None;
1157                #[cfg(not(target_arch = "wasm32"))]
1158                self.load_from_file(filename, load_options).ok();
1159                #[cfg(target_arch = "wasm32")]
1160                error!("Cannot load file from path in WASM");
1161            }
1162            Message::LoadWaveformFileFromUrl(url, load_options) => {
1163                self.user.selected_server_file_index = None;
1164                *self.surver_selected_file.borrow_mut() = None;
1165                // If we provide URL at command line and it is a Surver URL, we want to force a switch
1166                self.load_wave_from_url(url, load_options, true, None);
1167            }
1168            Message::LoadFromData(data, load_options) => {
1169                self.user.selected_server_file_index = None;
1170                *self.surver_selected_file.borrow_mut() = None;
1171                self.load_from_data(data, load_options).ok();
1172            }
1173            #[cfg(feature = "python")]
1174            Message::LoadPythonTranslator(filename) => {
1175                try_log_error!(
1176                    self.translators.load_python_translator(filename),
1177                    "Error loading Python translator",
1178                )
1179            }
1180            #[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
1181            Message::LoadWasmTranslator(path) => {
1182                let sender = self.channels.msg_sender.clone();
1183                perform_work(
1184                    move || match PluginTranslator::new(path.into_std_path_buf()) {
1185                        Ok(t) => {
1186                            checked_send(&sender, Message::TranslatorLoaded(Arc::new(t)));
1187                        }
1188                        Err(e) => {
1189                            error!("Failed to load wasm translator {e:#}");
1190                        }
1191                    },
1192                );
1193            }
1194            Message::LoadCommandFile(path) => {
1195                self.add_batch_commands(read_command_file(&path));
1196            }
1197            Message::LoadCommandFileFromUrl(url) => {
1198                self.load_commands_from_url(url);
1199            }
1200            Message::LoadCommandFromData(bytes) => {
1201                self.add_batch_commands(read_command_bytes(bytes));
1202            }
1203            Message::SetupCxxrtl(kind) => self.connect_to_cxxrtl(kind, false),
1204            Message::SetSurverStatus(_start, server, status) => {
1205                self.user.surver_file_infos = Some(status.file_infos.clone());
1206                info!(
1207                    "Received surfer server status from {server}. {} files available.",
1208                    status.file_infos.len()
1209                );
1210                self.user.surver_url = Some(server.clone());
1211                if status.file_infos.is_empty() {
1212                    warn!("Received surfer server status with no file infos");
1213                    return None;
1214                }
1215                if self.user.selected_server_file_index.is_none() {
1216                    if status.file_infos.len() == 1 {
1217                        // if only one file is available, select it automatically
1218                        info!(
1219                            "Only one file available on server {}, loading it automatically",
1220                            server
1221                        );
1222                        self.load_wave_from_url(server.clone(), LoadOptions::Clear, false, Some(0));
1223                    } else {
1224                        // if no file is selected, show the server file selection window
1225                        self.user.show_server_file_window = true;
1226                        self.progress_tracker = None;
1227                    }
1228                }
1229
1230                if let Some(file_index) = self.user.selected_server_file_index {
1231                    if file_index >= status.file_infos.len() {
1232                        warn!(
1233                            "Selected server file index {file_index} is out of bounds ({} files available)",
1234                            status.file_infos.len()
1235                        );
1236                        return None;
1237                    }
1238                    self.server_status_to_progress(&server, &status.file_infos[file_index]);
1239                }
1240            }
1241            Message::FileDropped(dropped_file) => {
1242                self.load_from_dropped(dropped_file)
1243                    .map_err(|e| error!("{e:#?}"))
1244                    .ok();
1245            }
1246            Message::StopProgressTracker => {
1247                self.progress_tracker = None;
1248            }
1249            Message::WaveHeaderLoaded(start, source, load_options, header) => {
1250                // for files using the `wellen` backend, we load the header before parsing the body
1251                info!(
1252                    "Loaded the hierarchy and meta-data of {source} in {:?}",
1253                    start.elapsed()
1254                );
1255                match header {
1256                    HeaderResult::LocalFile(header) => {
1257                        // register waveform as loaded (but with no variable info yet!)
1258                        let shared_hierarchy = Arc::new(header.hierarchy);
1259                        let new_waves =
1260                            Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1261                        self.on_waves_loaded(
1262                            source.clone(),
1263                            convert_format(header.file_format),
1264                            new_waves,
1265                            load_options,
1266                        );
1267                        // start parsing of the body
1268                        self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1269                    }
1270                    HeaderResult::LocalBytes(header) => {
1271                        // register waveform as loaded (but with no variable info yet!)
1272                        let shared_hierarchy = Arc::new(header.hierarchy);
1273                        let new_waves =
1274                            Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1275                        self.on_waves_loaded(
1276                            source.clone(),
1277                            convert_format(header.file_format),
1278                            new_waves,
1279                            load_options,
1280                        );
1281                        // start parsing of the body
1282                        self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1283                    }
1284                    HeaderResult::Remote(hierarchy, file_format, server, file_index) => {
1285                        // register waveform as loaded (but with no variable info yet!)
1286                        let new_waves = Box::new(WaveContainer::new_remote_waveform(
1287                            &server,
1288                            hierarchy.clone(),
1289                            file_index,
1290                        ));
1291                        self.on_waves_loaded(
1292                            source.clone(),
1293                            convert_format(file_format),
1294                            new_waves,
1295                            load_options,
1296                        );
1297                        // body is already being parsed on the server, we need to request the time table though
1298                        get_time_table_from_server(
1299                            self.channels.msg_sender.clone(),
1300                            server,
1301                            file_index,
1302                        );
1303                    }
1304                }
1305            }
1306            Message::WaveBodyLoaded(start, source, body) => {
1307                // for files using the `wellen` backend, parse the body in a second step
1308                info!("Loaded the body of {source} in {:?}", start.elapsed());
1309                self.progress_tracker = None;
1310                let waves = self
1311                    .user
1312                    .waves
1313                    .as_mut()
1314                    .expect("Waves should be loaded at this point!");
1315                // add source and time table
1316                let maybe_cmd = waves // TODO
1317                    .inner
1318                    .as_waves_mut()?
1319                    .wellen_add_body(body)
1320                    .map_err(|err| {
1321                        error!("While getting commands to lazy-load signals: {err:?}");
1322                    })
1323                    .ok()
1324                    .flatten();
1325                // Pre-load parameters
1326                let param_cmd = waves
1327                    .inner
1328                    .as_waves_mut()?
1329                    .load_parameters()
1330                    .map_err(|err| {
1331                        error!("While getting commands to lazy-load parameters: {err:?}");
1332                    })
1333                    .ok()
1334                    .flatten();
1335
1336                if self.wcp_greeted_signal.load(Ordering::Relaxed)
1337                    && self.wcp_client_capabilities.waveforms_loaded
1338                {
1339                    let source = match source {
1340                        WaveSource::File(path) => path.to_string(),
1341                        WaveSource::Url(url) => url,
1342                        _ => String::new(),
1343                    };
1344                    self.channels.wcp_s2c_sender.as_ref().map(|ch| {
1345                        block_on(
1346                            ch.send(WcpSCMessage::event(WcpEvent::waveforms_loaded { source })),
1347                        )
1348                    });
1349                }
1350
1351                // update viewports, now that we have the time table
1352                waves.update_viewports();
1353                // make sure we redraw
1354                self.invalidate_draw_commands();
1355                // start loading parameters
1356                if let Some(cmd) = param_cmd {
1357                    self.load_variables(cmd);
1358                }
1359                // start loading variables
1360                if let Some(cmd) = maybe_cmd {
1361                    self.load_variables(cmd);
1362                }
1363            }
1364            Message::SignalsLoaded(start, res) => {
1365                info!("Loaded {} variables in {:?}", res.len(), start.elapsed());
1366                self.progress_tracker = None;
1367                let waves = self
1368                    .user
1369                    .waves
1370                    .as_mut()
1371                    .expect("Waves should be loaded at this point!");
1372                match waves.inner.as_waves_mut()?.on_signals_loaded(res) {
1373                    Err(err) => error!("{err:?}"),
1374                    Ok(Some(cmd)) => self.load_variables(cmd),
1375                    _ => {}
1376                }
1377                // make sure we redraw since now more variable data is available
1378                self.invalidate_draw_commands();
1379            }
1380            Message::WavesLoaded(filename, format, new_waves, load_options) => {
1381                self.on_waves_loaded(filename, format, new_waves, load_options);
1382                // here, the body and thus the number of timestamps is already loaded!
1383                self.user
1384                    .waves
1385                    .as_mut()
1386                    .expect("Waves should be loaded at this point!")
1387                    .update_viewports();
1388                self.progress_tracker = None;
1389            }
1390            Message::TransactionStreamsLoaded(filename, format, new_ftr, loaded_options) => {
1391                self.on_transaction_streams_loaded(filename, format, new_ftr, loaded_options);
1392                self.user
1393                    .waves
1394                    .as_mut()
1395                    .expect("Waves should be loaded at this point!")
1396                    .update_viewports();
1397            }
1398            Message::BlacklistTranslator(idx, translator) => {
1399                self.user.blacklisted_translators.insert((idx, translator));
1400            }
1401            Message::Error(e) => {
1402                error!("{e:?}");
1403                self.user.show_logs = true;
1404            }
1405            Message::TranslatorLoaded(t) => {
1406                info!("Translator {} loaded", t.name());
1407                t.set_wave_source(
1408                    self.user
1409                        .waves
1410                        .as_ref()
1411                        .map(|waves| waves.source.into_translation_type()),
1412                );
1413
1414                self.translators.add_or_replace(AnyTranslator::Full(t));
1415            }
1416            Message::SetSidePanelVisible(v) => self.user.show_hierarchy = Some(v),
1417            Message::SetMenuVisible(v) => self.user.show_menu = Some(v),
1418            Message::ToggleMenu => {
1419                self.user.show_menu = Some(!self.show_menu());
1420            }
1421            Message::SetToolbarVisible(v) => self.user.show_toolbar = Some(v),
1422            Message::SetShowEmptyScopes(v) => self.user.show_empty_scopes = Some(v),
1423            Message::SetShowHierarchyIcons(v) => self.user.show_hierarchy_icons = Some(v),
1424            Message::SetParameterDisplayLocation(location) => {
1425                self.user.parameter_display_location = Some(location);
1426            }
1427            Message::SetStatusbarVisible(v) => self.user.show_statusbar = Some(v),
1428            Message::SetTickLines(v) => self.user.show_ticks = Some(v),
1429            Message::SetVariableTooltip(v) => self.user.show_tooltip = Some(v),
1430            Message::SetScopeTooltip(v) => self.user.show_scope_tooltip = Some(v),
1431            Message::SetOverviewVisible(v) => self.user.show_overview = Some(v),
1432            Message::SetShowVariableDirection(v) => self.user.show_variable_direction = Some(v),
1433            Message::SetTransitionValue(v) => self.user.transition_value = Some(v),
1434            Message::SetShowIndices(v) => {
1435                let new = v;
1436                self.user.show_variable_indices = Some(new);
1437                let waves = self.user.waves.as_mut()?;
1438                waves.display_variable_indices = new;
1439                waves.compute_variable_display_names();
1440            }
1441            Message::SetHighlightFocused(highlight) => {
1442                self.user.highlight_focused = Some(highlight);
1443            }
1444            Message::HideCommandPrompt => {
1445                *self.command_prompt_text.borrow_mut() = String::new();
1446                self.command_prompt.suggestions = vec![];
1447                self.command_prompt.selected = self.command_prompt.previous_commands.len();
1448                self.command_prompt.visible = false;
1449            }
1450            Message::ShowCommandPrompt(text, selected) => {
1451                self.command_prompt.new_text = Some((text, selected.unwrap_or(String::new())));
1452                self.command_prompt.visible = true;
1453            }
1454            Message::FileDownloaded(url, bytes, load_options) => {
1455                self.load_from_bytes(WaveSource::Url(url), bytes.to_vec(), load_options);
1456            }
1457            Message::CommandFileDownloaded(_url, bytes) => {
1458                self.add_batch_commands(read_command_bytes(bytes.to_vec()));
1459                self.progress_tracker = None;
1460            }
1461            Message::SetConfigFromString(s) => {
1462                // FIXME think about a structured way to collect errors
1463                let config = SurferConfig::new_from_toml(&s)
1464                    .with_context(|| "Failed to load config file")
1465                    .ok()?;
1466
1467                self.user.config = config;
1468
1469                let ctx = &self.context.as_ref()?;
1470                ctx.set_visuals(self.get_visuals());
1471            }
1472            Message::ReloadConfig => {
1473                // FIXME think about a structured way to collect errors
1474                let config = SurferConfig::new(false)
1475                    .with_context(|| "Failed to load config file")
1476                    .ok()?;
1477                self.translators = all_translators();
1478                self.user.config = config;
1479
1480                let ctx = &self.context.as_ref()?;
1481                ctx.set_visuals(self.get_visuals());
1482            }
1483            Message::ReloadWaveform(keep_unavailable) => {
1484                let waves = self.user.waves.as_ref()?;
1485                let options = if keep_unavailable {
1486                    LoadOptions::KeepAll
1487                } else {
1488                    LoadOptions::KeepAvailable
1489                };
1490                match &waves.source {
1491                    WaveSource::File(filename) => {
1492                        self.load_from_file(filename.clone(), options).ok();
1493                    }
1494                    WaveSource::Data => {}       // can't reload
1495                    WaveSource::Cxxrtl(..) => {} // can't reload
1496                    WaveSource::DragAndDrop(filename) => {
1497                        filename
1498                            .clone()
1499                            .and_then(|filename| self.load_from_file(filename, options).ok());
1500                    }
1501                    WaveSource::Url(url) => {
1502                        self.load_wave_from_url(
1503                            url.clone(),
1504                            options,
1505                            false,
1506                            self.user.selected_server_file_index,
1507                        );
1508                    }
1509                }
1510
1511                for translator in self.translators.all_translators() {
1512                    translator.reload(self.channels.msg_sender.clone());
1513                }
1514                self.variable_name_info_cache.borrow_mut().clear();
1515                self.translator_generation += 1;
1516
1517                if let Some(waves) = self.user.waves.as_mut() {
1518                    waves.compute_variable_display_names();
1519                }
1520            }
1521            Message::SuggestReloadWaveform => match self.autoreload_files() {
1522                AutoLoad::Always => self.update(Message::ReloadWaveform(true))?,
1523                AutoLoad::Never => (),
1524                AutoLoad::Ask => {
1525                    self.user.show_reload_suggestion = Some(ReloadWaveformDialog::default());
1526                }
1527            },
1528            Message::CloseReloadWaveformDialog {
1529                reload_file,
1530                do_not_show_again,
1531            } => {
1532                if do_not_show_again {
1533                    // FIXME: This is currently saved in state, but could be persisted in
1534                    // some setting.
1535                    self.user.autoreload_files = Some(AutoLoad::from_bool(reload_file));
1536                }
1537                self.user.show_reload_suggestion = None;
1538                if reload_file {
1539                    self.update(Message::ReloadWaveform(true));
1540                }
1541            }
1542            Message::UpdateReloadWaveformDialog(dialog) => {
1543                self.user.show_reload_suggestion = Some(dialog);
1544            }
1545            Message::OpenSiblingStateFile(open) => {
1546                if !open {
1547                    return None;
1548                }
1549                let waves = self.user.waves.as_ref()?;
1550                let state_file_path = waves.source.sibling_state_file()?;
1551                self.load_state_file(Some(state_file_path.clone().into_std_path_buf()));
1552            }
1553            Message::SuggestOpenSiblingStateFile => match self.autoload_sibling_state_files() {
1554                AutoLoad::Always => {
1555                    self.update(Message::OpenSiblingStateFile(true));
1556                }
1557                AutoLoad::Never => {}
1558                AutoLoad::Ask => {
1559                    self.user.show_open_sibling_state_file_suggestion =
1560                        Some(OpenSiblingStateFileDialog::default());
1561                }
1562            },
1563            Message::CloseOpenSiblingStateFileDialog {
1564                load_state,
1565                do_not_show_again,
1566            } => {
1567                if do_not_show_again {
1568                    self.user.autoload_sibling_state_files = Some(AutoLoad::from_bool(load_state));
1569                }
1570                self.user.show_open_sibling_state_file_suggestion = None;
1571                if load_state {
1572                    self.update(Message::OpenSiblingStateFile(true));
1573                }
1574            }
1575            Message::UpdateOpenSiblingStateFileDialog(dialog) => {
1576                self.user.show_open_sibling_state_file_suggestion = Some(dialog);
1577            }
1578            Message::RemovePlaceholders => {
1579                let waves = self.user.waves.as_mut()?;
1580                waves.remove_placeholders();
1581            }
1582            Message::SetClockHighlightType(new_type) => {
1583                self.user.clock_highlight_type = Some(new_type);
1584                self.invalidate_draw_commands();
1585            }
1586            Message::SetFillHighValues(fill) => self.user.fill_high_values = Some(fill),
1587            Message::SetDinotraceStyle(dino_style) => {
1588                self.user.use_dinotrace_style = Some(dino_style);
1589                self.invalidate_draw_commands();
1590            }
1591            Message::AddMarker {
1592                time,
1593                name,
1594                move_focus,
1595            } => {
1596                if let Some(name) = &name {
1597                    self.save_current_canvas(format!("Add marker {name} at {time}"));
1598                } else {
1599                    self.save_current_canvas(format!("Add marker at {time}"));
1600                }
1601                let waves = self.user.waves.as_mut()?;
1602                waves.add_marker(&time, name, move_focus);
1603            }
1604            Message::SetMarker { id, time } => {
1605                self.save_current_canvas(format!("Set marker {id} to {time}"));
1606                let waves = self.user.waves.as_mut()?;
1607                waves.set_marker_position(id, &time);
1608            }
1609            Message::RemoveMarker(id) => {
1610                let waves = self.user.waves.as_mut()?;
1611                waves.remove_marker(id);
1612            }
1613            Message::MoveMarkerToCursor(idx) => {
1614                self.save_current_canvas("Move marker".into());
1615                let waves = self.user.waves.as_mut()?;
1616                waves.move_marker_to_cursor(idx);
1617            }
1618            Message::GoToCursorIfNotInView => {
1619                let waves = self.user.waves.as_mut()?;
1620                if waves.go_to_cursor_if_not_in_view() {
1621                    self.invalidate_draw_commands();
1622                }
1623            }
1624            Message::GoToMarkerPosition(idx, viewport_idx) => {
1625                let waves = self.user.waves.as_mut()?;
1626                // If there are no timestamps, the file is not fully loaded
1627                if let Some(num_timestamps) = waves.num_timestamps() {
1628                    let cursor = waves.markers.get(&idx)?;
1629                    waves.viewports[viewport_idx].go_to_time(cursor, &num_timestamps);
1630                    self.invalidate_draw_commands();
1631                } else {
1632                    warn!(
1633                        "Go to marker position: No timestamps count, even though waveforms should be loaded"
1634                    );
1635                }
1636            }
1637            Message::ChangeVariableNameType(target, name_type) => {
1638                let waves = self.user.waves.as_mut()?;
1639                let recompute_names = waves.change_variable_name_type(target, name_type);
1640
1641                if recompute_names {
1642                    waves.compute_variable_display_names();
1643                }
1644            }
1645            Message::ForceVariableNameTypes(name_type) => {
1646                let waves = self.user.waves.as_mut()?;
1647                waves.force_variable_name_type(name_type);
1648            }
1649            Message::CommandPromptClear => {
1650                *self.command_prompt_text.borrow_mut() = String::new();
1651                self.command_prompt.suggestions = vec![];
1652                // self.command_prompt.selected = self.command_prompt.previous_commands.len();
1653                self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1654                    self.command_prompt.previous_commands.len().clamp(0, 3)
1655                } else {
1656                    0
1657                };
1658            }
1659            Message::CommandPromptUpdate { suggestions } => {
1660                self.command_prompt.suggestions = suggestions;
1661                self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1662                    self.command_prompt.previous_commands.len().clamp(0, 3)
1663                } else {
1664                    0
1665                };
1666                self.command_prompt.new_selection =
1667                    Some(if self.command_prompt_text.borrow().is_empty() {
1668                        self.command_prompt.previous_commands.len().clamp(0, 3)
1669                    } else {
1670                        0
1671                    });
1672            }
1673            Message::CommandPromptPushPrevious(cmd) => {
1674                let len = cmd.len();
1675                self.command_prompt
1676                    .previous_commands
1677                    .insert(0, (cmd, vec![false; len]));
1678            }
1679            Message::OpenFileDialog(mode) => {
1680                self.open_file_dialog(mode);
1681            }
1682            Message::OpenCommandFileDialog => {
1683                self.open_command_file_dialog();
1684            }
1685            #[cfg(feature = "python")]
1686            Message::OpenPythonPluginDialog => {
1687                self.open_python_file_dialog();
1688            }
1689            #[cfg(feature = "python")]
1690            Message::ReloadPythonPlugin => {
1691                try_log_error!(
1692                    self.translators.reload_python_translator(),
1693                    "Error reloading Python translator"
1694                );
1695                self.translator_generation += 1;
1696                self.invalidate_draw_commands();
1697            }
1698            Message::SaveStateFile(path) => self.save_state_file(path),
1699            Message::LoadStateFromData(bytes) => self.load_state_from_bytes(bytes),
1700            Message::LoadStateFile(path) => self.load_state_file(path),
1701            Message::LoadState(state, path) => self.load_state(state, path),
1702            Message::SetStateFile(path) => {
1703                // since in wasm we can't support "save", only "save as" - never set the `state_file`
1704                #[cfg(not(target_arch = "wasm32"))]
1705                {
1706                    self.user.state_file = Some(path);
1707                }
1708                #[cfg(target_arch = "wasm32")]
1709                {
1710                    error!("Failed to load {path:?}. Loading state files is unsupported on wasm")
1711                }
1712            }
1713            Message::SetAboutVisible(s) => self.user.show_about = s,
1714            Message::SetKeyHelpVisible(s) => self.user.show_keys = s,
1715            Message::SetGestureHelpVisible(s) => self.user.show_gestures = s,
1716            Message::SetUrlEntryVisible(s, f) => {
1717                self.user.show_url_entry = s;
1718                self.url_callback = f;
1719            }
1720            Message::SetLicenseVisible(s) => self.user.show_license = s,
1721            Message::SetQuickStartVisible(s) => self.user.show_quick_start = s,
1722            Message::SetPerformanceVisible(s) => {
1723                if !s {
1724                    self.continuous_redraw = false;
1725                }
1726                self.user.show_performance = s;
1727            }
1728            Message::SetContinuousRedraw(s) => self.continuous_redraw = s,
1729            Message::SetMouseGestureDragStart(pos, time) => {
1730                self.gesture_start_location = pos;
1731                self.gesture_start_time = time;
1732            }
1733            Message::SetMeasureDragStart(pos) => self.measure_start_location = pos,
1734            Message::SetFilterFocused(s) => self.user.variable_name_filter_focused = s,
1735            Message::SetTimeEditFocused(s) => self.time_edit_focused = s,
1736            Message::SetRequestTimeEditFocus(s) => self.request_time_edit_focus = s,
1737            Message::SetVariableNameFilterType(variable_name_filter_type) => {
1738                self.user.variable_filter.name_filter_type = variable_name_filter_type;
1739            }
1740            Message::SetVariableNameFilterCaseInsensitive(s) => {
1741                self.user.variable_filter.name_filter_case_insensitive = s;
1742            }
1743            Message::SetVariableIOFilter(t, b) => match t {
1744                VariableIOFilterType::Output => self.user.variable_filter.include_outputs = b,
1745                VariableIOFilterType::Input => self.user.variable_filter.include_inputs = b,
1746                VariableIOFilterType::InOut => self.user.variable_filter.include_inouts = b,
1747                VariableIOFilterType::Other => self.user.variable_filter.include_others = b,
1748            },
1749            Message::SetVariableGroupByDirection(b) => {
1750                self.user.variable_filter.group_by_direction = b;
1751            }
1752            Message::SetUIZoomFactor(scale) => {
1753                if let Some(ctx) = &mut self.context.as_ref() {
1754                    ctx.set_zoom_factor(scale);
1755                }
1756                self.user.ui_zoom_factor = Some(scale);
1757            }
1758            Message::SelectPrevCommand => {
1759                self.command_prompt.new_selection = Some(
1760                    self.command_prompt
1761                        .new_selection
1762                        .unwrap_or(self.command_prompt.selected)
1763                        .saturating_sub(1),
1764                );
1765            }
1766            Message::SelectNextCommand => {
1767                self.command_prompt.new_selection = Some(
1768                    self.command_prompt
1769                        .new_selection
1770                        .unwrap_or(self.command_prompt.selected)
1771                        .saturating_add(1)
1772                        .min(self.command_prompt.suggestions.len().saturating_sub(1)),
1773                );
1774            }
1775            Message::SetHierarchyStyle(style) => self.user.hierarchy_style = Some(style),
1776            Message::SetArrowKeyBindings(bindings) => {
1777                self.user.arrow_key_bindings = Some(bindings);
1778            }
1779            Message::SetPrimaryMouseDragBehavior(behavior) => {
1780                self.user.primary_button_drag_behavior = Some(behavior);
1781            }
1782            Message::InvalidateDrawCommands => self.invalidate_draw_commands(),
1783            Message::UnpauseSimulation => {
1784                let waves = self.user.waves.as_ref()?;
1785                waves.inner.as_waves()?.unpause_simulation();
1786            }
1787            Message::PauseSimulation => {
1788                let waves = self.user.waves.as_ref()?;
1789                waves.inner.as_waves()?.pause_simulation();
1790            }
1791            Message::Batch(messages) => {
1792                for message in messages {
1793                    self.update(message);
1794                }
1795            }
1796            Message::AddDraggedVariables(variables) => {
1797                let waves = self.user.waves.as_mut()?;
1798
1799                waves.focused_item = None;
1800                self.user.drag_source_idx = None;
1801                let target = self.user.drag_target_idx.take();
1802
1803                if let (Some(cmd), _) =
1804                    waves.add_variables(&self.translators, variables, target, true, false, None)
1805                {
1806                    self.load_variables(cmd);
1807                }
1808                self.invalidate_draw_commands();
1809            }
1810            Message::VariableDragStarted(vidx) => {
1811                self.user.drag_started = true;
1812                self.user.drag_source_idx = Some(vidx);
1813                self.user.drag_target_idx = None;
1814            }
1815            Message::VariableDragTargetChanged(position) => {
1816                self.user.drag_target_idx = Some(position);
1817            }
1818            Message::VariableDragFinished => {
1819                self.user.drag_started = false;
1820
1821                let source_vidx = self.user.drag_source_idx.take()?;
1822                let target_position = self.user.drag_target_idx.take()?;
1823
1824                // reordering
1825                self.save_current_canvas("Drag item".to_string());
1826                self.invalidate_draw_commands();
1827                let waves = self.user.waves.as_mut()?;
1828
1829                let focused_index = waves
1830                    .focused_item
1831                    .and_then(|vidx| waves.items_tree.to_displayed(vidx));
1832                let focused_item_ref = focused_index
1833                    .and_then(|idx| waves.items_tree.get(idx))
1834                    .map(|node| node.item_ref);
1835
1836                let mut to_move = waves
1837                    .items_tree
1838                    .iter_visible_extra()
1839                    .filter_map(|info| info.node.selected.then_some(info.idx))
1840                    .collect::<Vec<_>>();
1841                if let Some(idx) = focused_index {
1842                    to_move.push(idx);
1843                }
1844                if let Some(vidx) = waves.items_tree.to_displayed(source_vidx) {
1845                    to_move.push(vidx);
1846                }
1847
1848                let _ = waves.items_tree.move_items(to_move, target_position);
1849
1850                waves.focused_item = focused_item_ref
1851                    .and_then(|item_ref| {
1852                        waves
1853                            .items_tree
1854                            .iter_visible()
1855                            .position(|node| node.item_ref == item_ref)
1856                    })
1857                    .map(VisibleItemIndex);
1858            }
1859            Message::VariableValueToClipbord(vidx) => {
1860                self.handle_variable_clipboard_operation(
1861                    vidx,
1862                    |waves, item_ref: DisplayedItemRef| {
1863                        if let Some(DisplayedItem::Variable(_)) =
1864                            waves.displayed_items.get(&item_ref)
1865                        {
1866                            let field_ref = item_ref.into();
1867                            self.get_variable_value(
1868                                waves,
1869                                &field_ref,
1870                                waves
1871                                    .cursor
1872                                    .as_ref()
1873                                    .and_then(num::BigInt::to_biguint)
1874                                    .as_ref(),
1875                            )
1876                        } else {
1877                            None
1878                        }
1879                    },
1880                );
1881            }
1882            Message::VariableNameToClipboard(vidx) => {
1883                self.handle_variable_clipboard_operation(
1884                    vidx,
1885                    |waves, item_ref: DisplayedItemRef| {
1886                        if let Some(DisplayedItem::Variable(variable)) =
1887                            waves.displayed_items.get(&item_ref)
1888                        {
1889                            Some(variable.variable_ref.name.clone())
1890                        } else {
1891                            None
1892                        }
1893                    },
1894                );
1895            }
1896            Message::VariableFullNameToClipboard(vidx) => {
1897                self.handle_variable_clipboard_operation(
1898                    vidx,
1899                    |waves, item_ref: DisplayedItemRef| {
1900                        if let Some(DisplayedItem::Variable(variable)) =
1901                            waves.displayed_items.get(&item_ref)
1902                        {
1903                            Some(variable.variable_ref.full_path_string())
1904                        } else {
1905                            None
1906                        }
1907                    },
1908                );
1909            }
1910            Message::SetViewportStrategy(s) => {
1911                if let Some(waves) = &mut self.user.waves {
1912                    for vp in &mut waves.viewports {
1913                        vp.move_strategy = s;
1914                    }
1915                }
1916            }
1917            Message::Undo(count) => {
1918                let waves = self.user.waves.as_mut()?;
1919                for _ in 0..count {
1920                    if let Some(prev_state) = self.undo_stack.pop() {
1921                        self.redo_stack
1922                            .push(SystemState::current_canvas_state(waves, prev_state.message));
1923                        waves.focused_item = prev_state.focused_item;
1924                        waves.focused_transaction = prev_state.focused_transaction;
1925                        waves.items_tree = prev_state.items_tree;
1926                        waves.displayed_items = prev_state.displayed_items;
1927                        waves.markers = prev_state.markers;
1928                        waves.annotations = prev_state.annotations;
1929                        waves.annotation_groups = prev_state.annotation_group;
1930                        waves.annotation_list_visible = prev_state.annotation_list;
1931                        waves.selected_annotation = prev_state.selected_annotation;
1932                        waves.annotation_counter = prev_state.annotation_counter;
1933                    } else {
1934                        break;
1935                    }
1936                }
1937                self.invalidate_draw_commands();
1938            }
1939            Message::Redo(count) => {
1940                let waves = self.user.waves.as_mut()?;
1941                for _ in 0..count {
1942                    if let Some(prev_state) = self.redo_stack.pop() {
1943                        self.undo_stack
1944                            .push(SystemState::current_canvas_state(waves, prev_state.message));
1945                        waves.focused_item = prev_state.focused_item;
1946                        waves.focused_transaction = prev_state.focused_transaction;
1947                        waves.items_tree = prev_state.items_tree;
1948                        waves.displayed_items = prev_state.displayed_items;
1949                        waves.markers = prev_state.markers;
1950                        waves.annotations = prev_state.annotations;
1951                        waves.annotation_groups = prev_state.annotation_group;
1952                        waves.annotation_list_visible = prev_state.annotation_list;
1953                        waves.selected_annotation = prev_state.selected_annotation;
1954                        waves.annotation_counter = prev_state.annotation_counter;
1955                    } else {
1956                        break;
1957                    }
1958                }
1959                self.invalidate_draw_commands();
1960            }
1961            Message::DumpTree => {
1962                let waves = self.user.waves.as_ref()?;
1963                dump_tree(waves);
1964            }
1965            Message::GroupNew {
1966                name,
1967                before,
1968                items,
1969            } => {
1970                self.save_current_canvas(format!(
1971                    "Create group {}",
1972                    name.clone().unwrap_or(String::new())
1973                ));
1974                self.invalidate_draw_commands();
1975                let waves = self.user.waves.as_mut()?;
1976
1977                let passed_or_focused = before
1978                    .and_then(|before| {
1979                        waves
1980                            .items_tree
1981                            .get(before)
1982                            .map(|node| node.level)
1983                            .map(|level| TargetPosition { before, level })
1984                    })
1985                    .or_else(|| waves.insert_position(waves.focused_item));
1986                let final_target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
1987
1988                let mut item_refs = items.unwrap_or_else(|| {
1989                    waves
1990                        .items_tree
1991                        .iter_visible_selected()
1992                        .map(|node| node.item_ref)
1993                        .collect::<Vec<_>>()
1994                });
1995
1996                // if we are using the focus as the insert anchor, then move that as well
1997                let item_refs = if before.is_none() & passed_or_focused.is_some() {
1998                    let focus_index = waves
1999                        .items_tree
2000                        .to_displayed(waves.focused_item.expect("Inconsistent state"))
2001                        .expect("Inconsistent state");
2002                    item_refs.push(
2003                        waves
2004                            .items_tree
2005                            .get(focus_index)
2006                            .expect("Inconsistent state")
2007                            .item_ref,
2008                    );
2009                    item_refs
2010                } else {
2011                    item_refs
2012                };
2013
2014                if item_refs.is_empty() {
2015                    return None;
2016                }
2017
2018                let group_ref =
2019                    waves.add_group(name.unwrap_or("Group".to_owned()), Some(final_target));
2020
2021                let item_idxs = waves
2022                    .items_tree
2023                    .iter()
2024                    .enumerate()
2025                    .filter_map(|(idx, node)| {
2026                        item_refs
2027                            .contains(&node.item_ref)
2028                            .then_some(crate::displayed_item_tree::ItemIndex(idx))
2029                    })
2030                    .collect::<Vec<_>>();
2031
2032                if let Err(e) = waves.items_tree.move_items(
2033                    item_idxs,
2034                    crate::displayed_item_tree::TargetPosition {
2035                        before: ItemIndex(final_target.before.0 + 1),
2036                        level: final_target.level.saturating_add(1),
2037                    },
2038                ) {
2039                    dump_tree(waves);
2040                    error!("failed to move items into group: {e:?}");
2041                }
2042                waves.items_tree.xselect_all_visible(false);
2043                waves.focused_item = waves
2044                    .items_tree
2045                    .iter_visible_extra()
2046                    .find_map(|info| (info.node.item_ref == group_ref).then_some(info.vidx));
2047            }
2048            Message::GroupDissolve(item_ref) => {
2049                self.save_current_canvas("Dissolve group".to_owned());
2050                self.invalidate_draw_commands();
2051                let waves = self.user.waves.as_mut()?;
2052                let item_index = waves.index_for_ref_or_focus(item_ref)?;
2053
2054                let removed = waves.items_tree.remove_dissolve(item_index);
2055                waves.displayed_items.remove(&removed);
2056            }
2057            Message::GroupFold(item_ref)
2058            | Message::GroupUnfold(item_ref)
2059            | Message::GroupFoldRecursive(item_ref)
2060            | Message::GroupUnfoldRecursive(item_ref) => {
2061                let unfold = matches!(
2062                    message,
2063                    Message::GroupUnfold(..) | Message::GroupUnfoldRecursive(..)
2064                );
2065                let recursive = matches!(
2066                    message,
2067                    Message::GroupFoldRecursive(..) | Message::GroupUnfoldRecursive(..)
2068                );
2069
2070                let undo_msg = if unfold {
2071                    "Unfold group".to_owned()
2072                } else {
2073                    "Fold group".to_owned()
2074                } + &(if recursive {
2075                    " recursive".to_owned()
2076                } else {
2077                    String::new()
2078                });
2079                // undo message even if no waves are available
2080                self.save_current_canvas(undo_msg);
2081                self.invalidate_draw_commands();
2082
2083                let waves = self.user.waves.as_mut()?;
2084                let item = waves.index_for_ref_or_focus(item_ref)?;
2085
2086                if let Some(focused_item) = waves.focused_item {
2087                    let info = waves
2088                        .items_tree
2089                        .get_visible_extra(focused_item)
2090                        .expect("Inconsistent state");
2091                    if waves.items_tree.subtree_contains(item, info.idx) {
2092                        waves.focused_item = None;
2093                    }
2094                }
2095                if recursive {
2096                    waves.items_tree.xfold_recursive(item, unfold);
2097                } else {
2098                    waves.items_tree.xfold(item, unfold);
2099                }
2100            }
2101            Message::GroupFoldAll | Message::GroupUnfoldAll => {
2102                let unfold = matches!(message, Message::GroupUnfoldAll);
2103                let undo_msg = if unfold {
2104                    "Fold all groups".to_owned()
2105                } else {
2106                    "Unfold all groups".to_owned()
2107                };
2108                self.save_current_canvas(undo_msg);
2109                self.invalidate_draw_commands();
2110
2111                let waves = self.user.waves.as_mut()?;
2112
2113                // remove focus if focused item is folded away -> prevent future waveform
2114                // adds being invisibly inserted
2115                if let Some(focused_item) = waves.focused_item {
2116                    let focused_level = waves
2117                        .items_tree
2118                        .get_visible(focused_item)
2119                        .expect("Inconsistent state")
2120                        .level;
2121                    if !unfold && (focused_level > 0) {
2122                        waves.focused_item = None;
2123                    }
2124                }
2125                waves.items_tree.xfold_all(unfold);
2126            }
2127            #[cfg(target_arch = "wasm32")]
2128            Message::StartWcpServer { .. } => {
2129                error!("Wcp is not supported on wasm")
2130            }
2131            #[cfg(target_arch = "wasm32")]
2132            Message::StopWcpServer => {
2133                error!("Wcp is not supported on wasm")
2134            }
2135            #[cfg(not(target_arch = "wasm32"))]
2136            Message::StartWcpServer { address, initiate } => {
2137                self.start_wcp_server(address, initiate);
2138            }
2139            #[cfg(not(target_arch = "wasm32"))]
2140            Message::StopWcpServer => {
2141                self.stop_wcp_server();
2142            }
2143            Message::SetupChannelWCP => {
2144                #[cfg(target_arch = "wasm32")]
2145                {
2146                    use futures::executor::block_on;
2147                    self.channels.wcp_c2s_receiver = block_on(WCP_CS_HANDLER.rx.write()).take();
2148                    if self.channels.wcp_c2s_receiver.is_none() {
2149                        error!("Failed to claim wasm tx, was SetupWasmWCP executed twice?");
2150                    }
2151                    self.channels.wcp_s2c_sender = Some(WCP_SC_HANDLER.tx.clone());
2152                }
2153            }
2154            Message::BuildAnalogCache {
2155                display_id,
2156                cache_key,
2157            } => {
2158                let waves = self.user.waves.as_mut()?;
2159                let generation = waves.cache_generation;
2160
2161                // Check if already have valid entry (building or ready)
2162                let item = waves.displayed_items.get(&display_id)?;
2163                let DisplayedItem::Variable(var) = item else {
2164                    return None;
2165                };
2166                if var
2167                    .analog
2168                    .as_ref()?
2169                    .cache
2170                    .as_ref()
2171                    .is_some_and(|e| e.generation == generation && e.cache_key == cache_key)
2172                {
2173                    return None;
2174                }
2175
2176                // Try to share from in-flight builds first (handles removed-but-still-building case)
2177                if let Some(entry) = waves.inflight_caches.get(&cache_key)
2178                    && entry.generation == generation
2179                {
2180                    if let DisplayedItem::Variable(var) =
2181                        waves.displayed_items.get_mut(&display_id)?
2182                    {
2183                        var.analog.as_mut()?.cache = Some(entry.clone());
2184                    }
2185                    return None; // Shared from in-flight build
2186                }
2187
2188                // Try to share from another displayed variable (O(n) scan - only during cache build)
2189                let existing = waves
2190                    .displayed_items
2191                    .values()
2192                    .filter_map(|item| match item {
2193                        DisplayedItem::Variable(v) => v.analog.as_ref()?.cache.as_ref(),
2194                        _ => None,
2195                    })
2196                    .find(|e| e.cache_key == cache_key && e.generation == generation)
2197                    .cloned();
2198
2199                if let Some(entry) = existing {
2200                    if let DisplayedItem::Variable(var) =
2201                        waves.displayed_items.get_mut(&display_id)?
2202                    {
2203                        var.analog.as_mut()?.cache = Some(entry);
2204                    }
2205                    return None; // Shared existing entry (may still be building)
2206                }
2207
2208                // Clone variable_ref only when we need to spawn builder
2209                let variable_ref = match waves.displayed_items.get(&display_id)? {
2210                    DisplayedItem::Variable(v) => v.variable_ref.clone(),
2211                    _ => return None,
2212                };
2213
2214                // Create new entry and spawn builder
2215                let entry = std::sync::Arc::new(crate::analog_signal_cache::AnalogCacheEntry::new(
2216                    cache_key.clone(),
2217                    generation,
2218                ));
2219
2220                if let DisplayedItem::Variable(var) = waves.displayed_items.get_mut(&display_id)? {
2221                    var.analog.as_mut()?.cache = Some(entry.clone());
2222                }
2223
2224                let translator = self.translators.clone_translator(&cache_key.1);
2225
2226                // Track in-flight build for sharing with other variables
2227                waves
2228                    .inflight_caches
2229                    .insert(cache_key.clone(), entry.clone());
2230
2231                waves.build_analog_cache_async(
2232                    entry,
2233                    &variable_ref,
2234                    translator,
2235                    &self.channels.msg_sender,
2236                );
2237            }
2238            Message::AnalogCacheBuilt { entry, result } => {
2239                OUTSTANDING_TRANSACTIONS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
2240                // Remove from in-flight registry (may already be gone if generation changed)
2241                if let Some(waves) = self.user.waves.as_mut() {
2242                    waves.inflight_caches.remove(&entry.cache_key);
2243                }
2244                match result {
2245                    Ok(cache) => {
2246                        entry.set(cache);
2247                    }
2248                    Err(err) => {
2249                        warn!("Failed to build analog cache: {err}");
2250                    }
2251                }
2252                self.invalidate_draw_commands();
2253            }
2254            Message::Exit | Message::ToggleFullscreen => {} // Handled in eframe::update
2255            Message::AddViewport => {
2256                let waves = self.user.waves.as_mut()?;
2257                let viewport = Viewport::new();
2258                waves.viewports.push(viewport);
2259                self.draw_data.borrow_mut().push(None);
2260            }
2261            Message::RemoveViewport => {
2262                let waves = self.user.waves.as_mut()?;
2263                if waves.viewports.len() > 1 {
2264                    waves.viewports.pop();
2265                    self.draw_data.borrow_mut().pop();
2266                }
2267            }
2268            Message::SelectTheme(theme_name) => {
2269                let theme = SurferTheme::new(theme_name)
2270                    .with_context(|| "Failed to set theme")
2271                    .ok()?;
2272                self.user.config.theme = theme;
2273                let ctx = self.context.as_ref()?;
2274                ctx.set_visuals(self.get_visuals());
2275            }
2276            Message::EnableAnimations(enable) => {
2277                let ctx = self.context.as_ref()?;
2278                self.user.animation_enabled = Some(enable);
2279                ctx.all_styles_mut(|style| {
2280                    style.animation_time = if self.animation_enabled() {
2281                        self.user.config.animation_time
2282                    } else {
2283                        0.0
2284                    };
2285                });
2286            }
2287            Message::ShowDividerText(show) => {
2288                self.user.config.show_divider_text = show;
2289            }
2290            Message::AsyncDone(_) => (),
2291            Message::AddGraphic(id, g) => {
2292                let waves = self.user.waves.as_mut()?;
2293                waves.graphics.insert(id, g);
2294            }
2295            Message::RemoveGraphic(id) => {
2296                let waves = self.user.waves.as_mut()?;
2297                waves.graphics.retain(|k, _| k != &id);
2298            }
2299            Message::ExpandDrawnItem { item, levels } => {
2300                self.items_to_expand.borrow_mut().push((item, levels));
2301            }
2302            Message::SetMouseGestureAnnotation(annotation_kind) => {
2303                self.annotation_kind = annotation_kind;
2304            }
2305            Message::RectangleAdded {
2306                time_at_start,
2307                time_at_end,
2308                wave_from,
2309                wave_to,
2310                rect,
2311            } => {
2312                let id = self.annotation_id();
2313                self.save_current_canvas(format!("Add rectangle {id:?}"));
2314                let waves = self.user.waves.as_mut()?;
2315                waves.annotation_counter += 1;
2316
2317                let new_rect = Annotation::Rect(RectAnnotation::new(
2318                    id,
2319                    time_at_start,
2320                    time_at_end,
2321                    wave_from,
2322                    wave_to,
2323                    rect,
2324                    waves.annotation_counter,
2325                ));
2326                let new_id = new_rect.get_id();
2327                waves.annotations.push(new_rect);
2328
2329                waves.add_annotation_to_group(String::from("Ungrouped"), new_id);
2330            }
2331            Message::ArrowAdded {
2332                wave_point_from,
2333                wave_point_to,
2334                head_mode,
2335            } => {
2336                let id = self.annotation_id();
2337                self.save_current_canvas(format!("Add arrow {id:?}"));
2338                let waves = self.user.waves.as_mut()?;
2339                waves.annotation_counter += 1;
2340                let new_arrow = Annotation::Arrow(ArrowAnnotation::new(
2341                    id,
2342                    wave_point_from,
2343                    wave_point_to,
2344                    head_mode,
2345                    waves.annotation_counter,
2346                ));
2347                let new_id = new_arrow.get_id();
2348                waves.annotations.push(new_arrow);
2349
2350                waves.add_annotation_to_group(String::from("Ungrouped"), new_id);
2351            }
2352
2353            Message::RemoveAnnotation(anno_id) => {
2354                self.save_current_canvas(format!("Removed annotation {anno_id:?}"));
2355                let waves = self.user.waves.as_mut()?;
2356                waves.delete_annotation(anno_id);
2357                waves.remove_annotation_from_group(anno_id);
2358            }
2359
2360            Message::ToggleAnnotationVisiblility(anno_id) => {
2361                self.save_current_canvas(format!("Changed visibility on {anno_id:?}"));
2362                let waves = self.user.waves.as_mut()?;
2363                if let Some(target) = waves.annotations.iter_mut().find(|a| a.get_id() == anno_id) {
2364                    target.set_visibility(!target.is_visible());
2365                }
2366            }
2367
2368            Message::ToggleAnnotationListShowComments(anno_id) => {
2369                let waves = self.user.waves.as_mut()?;
2370                if let Some(target) = waves.annotations.iter_mut().find(|a| a.get_id() == anno_id) {
2371                    target.set_show_comments(!target.show_comments());
2372                }
2373            }
2374
2375            Message::GoToAnnotationPosition(anno_id, viewport_idx) => {
2376                let waves = self.user.waves.as_mut()?;
2377                //If there are no timestamps, the file is not fully loaded
2378                if let Some(num_timestamps) = waves.num_timestamps() {
2379                    if let Some(target) = waves.get_annotation_by_id(&anno_id) {
2380                        let mut left = target.get_start_time();
2381                        let mut right = target.get_end_time();
2382                        let from_wave = target.get_from_wave();
2383                        let to_wave = target.get_to_wave();
2384
2385                        let difference = (&right - &left) / 2;
2386                        left -= &difference;
2387                        right += difference;
2388                        waves.viewports[viewport_idx].zoom_to_range(&left, &right, &num_timestamps);
2389
2390                        if let Some(from_wave) = from_wave {
2391                            if let Some(to_wave) = to_wave {
2392                                if let Some(y_1) = waves.get_item_y(&from_wave) {
2393                                    if let Some(y_2) = waves.get_item_y(&to_wave) {
2394                                        // let y_diff = (y_2 - y_1) / 2.0;
2395                                        // let center = y_1 + y_diff;
2396                                        if let Some(item) = waves.get_item_at_y(y_1.min(y_2)) {
2397                                            waves.scroll_to_item(item.0);
2398                                        }
2399                                    } else {
2400                                        warn!(
2401                                            "GoToAnnotationPosition: got None from get_item_y(&to_wave)"
2402                                        );
2403                                    }
2404                                } else {
2405                                    warn!(
2406                                        "GoToAnnotationPosition: got None from get_item_y(&from_wave)"
2407                                    );
2408                                }
2409                            } else {
2410                                warn!("GoToAnnotationPosition: got None from to_wave");
2411                            }
2412                        } else {
2413                            warn!("GoToAnnotationPosition: got None from from_wave");
2414                        }
2415                    }
2416
2417                    self.invalidate_draw_commands();
2418                } else {
2419                    warn!(
2420                        "Go to marker position: No timestamps count, even though waveforms should be loaded"
2421                    );
2422                }
2423            }
2424
2425            Message::ToggleAnnotationlistVisibility() => {
2426                let waves = self.user.waves.as_mut()?;
2427                waves.annotation_list_visible = !waves.annotation_list_visible;
2428                self.user.show_annotation_list = waves.annotation_list_visible;
2429            }
2430
2431            Message::CreateAnnotationGroup(name) => {
2432                self.save_current_canvas(format!("Added annotation group {name}"));
2433                let waves = self.user.waves.as_mut()?;
2434
2435                let new_group = AnnotationGroup {
2436                    name: name.clone(),
2437                    cycle_counter: 0,
2438                    annotations: Vec::new(),
2439                };
2440
2441                waves.annotation_groups.push(new_group);
2442            }
2443
2444            Message::DeleteAnnotationGroup(name) => {
2445                self.save_current_canvas(format!("Removed annotation group {name}"));
2446                let waves = self.user.waves.as_mut()?;
2447
2448                waves.delete_group(name);
2449            }
2450
2451            Message::DeleteAllAnnotationInGroup(name) => {
2452                self.save_current_canvas(format!(
2453                    "Removed annotation group {name} and all it's annotations"
2454                ));
2455                let waves = self.user.waves.as_mut()?;
2456
2457                waves.remove_all_annotations_from_group(name);
2458            }
2459
2460            Message::AddCharToPrompt(c) => *self.char_to_add_to_prompt.borrow_mut() = Some(c),
2461
2462            Message::UpdateAnnotationGroup(anno_id, name) => {
2463                self.save_current_canvas(format!("Added {anno_id:?} to {name:?}"));
2464                let waves = self.user.waves.as_mut()?;
2465
2466                let target = waves.remove_annotation_from_group(anno_id);
2467
2468                match target {
2469                    Some(id) => {
2470                        waves.add_annotation_to_group(name?, id);
2471                    }
2472                    None => {
2473                        warn!("Error: Just removed non existent id!");
2474                    }
2475                }
2476            }
2477
2478            Message::UpdateAnnotationName(anno_id, name) => {
2479                self.save_current_canvas(format!("Changed {anno_id:?} name to {name}"));
2480                let waves = self.user.waves.as_mut()?;
2481                if let Some(target) = waves.annotations.iter_mut().find(|a| a.get_id() == anno_id) {
2482                    target.set_name(name);
2483                }
2484            }
2485
2486            Message::SetGroupVisibility(group, visible) => {
2487                self.save_current_canvas(format!("Changed group {:?} visibility", group.name));
2488                if let Some(waves) = self.user.waves.as_mut() {
2489                    for annotation_id in group.annotations {
2490                        for annotation in &mut waves.annotations {
2491                            if annotation_id == annotation.get_id() {
2492                                annotation.set_visibility(visible);
2493                            }
2494                        }
2495                    }
2496                }
2497            }
2498
2499            Message::AnnotationClicked(id, menu_pos, viewport_idx, to_screen, frame_width) => {
2500                if let Some(waves) = self.user.waves.as_mut() {
2501                    waves.select_annotation(id);
2502
2503                    let num_timestamps: BigInt = waves.safe_num_timestamps();
2504
2505                    let menu_pos_local = to_screen?.inverse().transform_pos(menu_pos?);
2506
2507                    let menu_pos_time: BigInt = waves.viewports[viewport_idx?].as_time_bigint(
2508                        menu_pos_local.x,
2509                        frame_width?,
2510                        &num_timestamps,
2511                    );
2512
2513                    waves.annotation_menu_time = Some(menu_pos_time);
2514
2515                    waves.annotation_menu_pos = if id.is_some() { menu_pos } else { None };
2516                }
2517            }
2518
2519            Message::SetActiveViewport(idx) => {
2520                if let Some(waves) = self.user.waves.as_mut()
2521                    && idx < waves.viewports.len()
2522                {
2523                    waves.last_active_viewport_idx = idx;
2524                }
2525            }
2526
2527            Message::RemoveCommentMessage(annotation_id, message_id) => {
2528                //self.save_current_canvas(format!("Removed message"));
2529                let waves = self.user.waves.as_mut()?;
2530                if let Some(target) = waves
2531                    .annotations
2532                    .iter_mut()
2533                    .find(|a| a.get_id() == annotation_id)
2534                {
2535                    target
2536                        .get_comment_box_mut()
2537                        .message_chain
2538                        .retain(|comment_message| comment_message.id != message_id);
2539                }
2540            }
2541
2542            Message::ClickHandled() => {
2543                self.click_handled = true;
2544            }
2545            Message::UpdateCommentBox(changes) => {
2546                let waves = self.user.waves.as_mut()?;
2547                for (annotation_id, comment) in changes {
2548                    if let Some(target) = waves
2549                        .annotations
2550                        .iter_mut()
2551                        .find(|a| a.get_id() == annotation_id)
2552                    {
2553                        target.update_comment_box(comment);
2554                    }
2555                }
2556            }
2557            Message::AddCommentMessage(annotation_id, message, user) => {
2558                //self.save_current_canvas(format!("Added message"));
2559                let waves = self.user.waves.as_mut()?;
2560                if let Some(target) = waves
2561                    .annotations
2562                    .iter_mut()
2563                    .find(|a| a.get_id() == annotation_id)
2564                {
2565                    let comment = target.get_comment_box_mut();
2566                    comment.message_chain.push(CommentMessage {
2567                        id: egui::Id::new(("comment", comment.message_id_source)),
2568                        user,
2569                        text: message,
2570                    });
2571                    comment.message_id_source += 1;
2572                }
2573            }
2574            Message::ToggleCommentVisibility(annotation_id) => {
2575                let waves = self.user.waves.as_mut()?;
2576                if let Some(target) = waves
2577                    .annotations
2578                    .iter_mut()
2579                    .find(|a| a.get_id() == annotation_id)
2580                {
2581                    target.get_comment_box_mut().visible = !target.get_comment_box().visible;
2582                }
2583            }
2584        }
2585
2586        Some(())
2587    }
2588
2589    fn annotation_id(&mut self) -> Id {
2590        let id = egui::Id::new(("annotation", self.annotation_id_source));
2591        self.annotation_id_source += 1;
2592        id
2593    }
2594
2595    pub fn add_scope_as_group(
2596        &mut self,
2597        scope: &ScopeRef,
2598        pos: TargetPosition,
2599        recursive: bool,
2600        variable_name_type: Option<VariableNameType>,
2601    ) -> TargetPosition {
2602        let Some(waves) = self.user.waves.as_mut() else {
2603            return pos;
2604        };
2605        let Some(container) = waves.inner.as_waves() else {
2606            return pos;
2607        };
2608
2609        let variables = container
2610            .variables_in_scope(scope)
2611            .iter()
2612            .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
2613            .cloned()
2614            .collect_vec();
2615        let child_scopes = container.child_scopes(scope);
2616        let variable_name_type = variable_name_type.or_else(|| {
2617            container
2618                .scope_is_variable(scope)
2619                .then_some(VariableNameType::Local)
2620        });
2621
2622        waves.add_group(scope.name(), Some(pos));
2623        let into_group_pos = TargetPosition {
2624            before: ItemIndex(pos.before.0 + 1),
2625            level: pos.level + 1,
2626        };
2627
2628        let (cmd, variable_refs) = waves.add_variables(
2629            &self.translators,
2630            variables,
2631            Some(into_group_pos),
2632            false,
2633            false,
2634            variable_name_type,
2635        );
2636        let mut into_group_pos = TargetPosition {
2637            before: ItemIndex(into_group_pos.before.0 + variable_refs.len()),
2638            level: pos.level + 1,
2639        };
2640
2641        if let Some(cmd) = cmd {
2642            self.load_variables(cmd);
2643        }
2644
2645        if recursive {
2646            for child in child_scopes.unwrap_or(vec![]) {
2647                into_group_pos =
2648                    self.add_scope_as_group(&child, into_group_pos, recursive, variable_name_type);
2649                into_group_pos.level = pos.level + 1;
2650            }
2651        }
2652        into_group_pos
2653    }
2654
2655    fn handle_variable_clipboard_operation<F>(
2656        &self,
2657        vidx: MessageTarget<VisibleItemIndex>,
2658        get_text: F,
2659    ) where
2660        F: FnOnce(&WaveData, DisplayedItemRef) -> Option<String>,
2661    {
2662        let Some(waves) = &self.user.waves else {
2663            return;
2664        };
2665        let vidx = if let MessageTarget::Explicit(vidx) = vidx {
2666            vidx
2667        } else if let Some(focused) = waves.focused_item {
2668            focused
2669        } else {
2670            return;
2671        };
2672        let Some(item_ref) = waves.items_tree.get_visible(vidx).map(|node| node.item_ref) else {
2673            return;
2674        };
2675
2676        if let Some(text) = get_text(waves, item_ref)
2677            && let Some(ctx) = &self.context
2678        {
2679            ctx.copy_text(text);
2680        }
2681    }
2682}
2683
2684fn dump_tree(waves: &WaveData) {
2685    let mut result = String::new();
2686    for (idx, node) in waves.items_tree.iter().enumerate() {
2687        for _ in 0..node.level.saturating_sub(1) {
2688            result.push(' ');
2689        }
2690
2691        if node.level > 0 {
2692            match waves.items_tree.get(ItemIndex(idx + 1)) {
2693                Some(next) if next.level < node.level => result.push_str("╰╴"),
2694                _ => result.push_str("├╴"),
2695            }
2696        }
2697
2698        result.push_str(
2699            &waves
2700                .displayed_items
2701                .get(&node.item_ref)
2702                .map_or("?".to_owned(), displayed_item::DisplayedItem::name),
2703        );
2704        result.push_str(&format!("   ({:?})", node.item_ref));
2705        if node.selected {
2706            result.push_str(" !SEL! ");
2707        }
2708        result.push('\n');
2709    }
2710    info!("tree: \n{}", &result);
2711}
2712
2713pub struct StateWrapper(Arc<RwLock<SystemState>>);
2714impl App for StateWrapper {
2715    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
2716        App::ui(&mut *self.0.write().unwrap(), ui, frame);
2717    }
2718}