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