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