oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"""User preferences management system.
|
|
2
|
+
|
|
3
|
+
This module provides persistent user preferences for Oscura including
|
|
4
|
+
visualization settings, default parameters, export options, and UI
|
|
5
|
+
preferences.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from oscura.config.schema import validate_against_schema
|
|
19
|
+
from oscura.core.exceptions import ConfigurationError
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class VisualizationPreferences:
|
|
26
|
+
"""Visualization preferences.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
style: Matplotlib style name
|
|
30
|
+
figure_size: Default figure size (width, height) in inches
|
|
31
|
+
dpi: Default DPI for figures
|
|
32
|
+
colormap: Default colormap name
|
|
33
|
+
grid: Whether to show grid by default
|
|
34
|
+
dark_mode: Use dark theme
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
style: str = "seaborn-v0_8-whitegrid"
|
|
38
|
+
figure_size: tuple[float, float] = (10, 6)
|
|
39
|
+
dpi: int = 100
|
|
40
|
+
colormap: str = "viridis"
|
|
41
|
+
grid: bool = True
|
|
42
|
+
dark_mode: bool = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class DefaultsPreferences:
|
|
47
|
+
"""Default analysis parameters.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
sample_rate: Default sample rate (Hz)
|
|
51
|
+
window_function: Default FFT window
|
|
52
|
+
fft_size: Default FFT size
|
|
53
|
+
rise_time_thresholds: Default rise time thresholds (low, high)
|
|
54
|
+
logic_family: Default logic family
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
sample_rate: float = 1e9 # 1 GHz
|
|
58
|
+
window_function: str = "hann"
|
|
59
|
+
fft_size: int = 8192
|
|
60
|
+
rise_time_thresholds: tuple[float, float] = (10.0, 90.0)
|
|
61
|
+
logic_family: str = "TTL"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ExportPreferences:
|
|
66
|
+
"""Export preferences.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
default_format: Default export format
|
|
70
|
+
precision: Floating point precision (decimal places)
|
|
71
|
+
include_metadata: Include metadata in exports
|
|
72
|
+
compression: Default compression for HDF5
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
default_format: str = "csv"
|
|
76
|
+
precision: int = 6
|
|
77
|
+
include_metadata: bool = True
|
|
78
|
+
compression: str = "gzip"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class LoggingPreferences:
|
|
83
|
+
"""Logging preferences.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
level: Default log level
|
|
87
|
+
file: Log file path (None for no file logging)
|
|
88
|
+
format: Log format string
|
|
89
|
+
show_timestamps: Show timestamps in console
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
level: str = "WARNING"
|
|
93
|
+
file: str | None = None
|
|
94
|
+
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
95
|
+
show_timestamps: bool = False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class EditorPreferences:
|
|
100
|
+
"""Editor/REPL preferences.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
history_size: Number of commands to keep in history
|
|
104
|
+
auto_save: Auto-save session on exit
|
|
105
|
+
syntax_highlighting: Enable syntax highlighting
|
|
106
|
+
tab_completion: Enable tab completion
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
history_size: int = 1000
|
|
110
|
+
auto_save: bool = True
|
|
111
|
+
syntax_highlighting: bool = True
|
|
112
|
+
tab_completion: bool = True
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class UserPreferences:
|
|
117
|
+
"""Complete user preferences.
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
visualization: Visualization preferences
|
|
121
|
+
defaults: Default analysis parameters
|
|
122
|
+
export: Export preferences
|
|
123
|
+
logging: Logging preferences
|
|
124
|
+
editor: Editor/REPL preferences
|
|
125
|
+
recent_files: List of recent file paths
|
|
126
|
+
custom: Custom user-defined preferences
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
>>> prefs = UserPreferences()
|
|
130
|
+
>>> prefs.visualization.dark_mode = True
|
|
131
|
+
>>> prefs.defaults.sample_rate = 2e9
|
|
132
|
+
>>> prefs.save()
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
visualization: VisualizationPreferences = field(default_factory=VisualizationPreferences)
|
|
136
|
+
defaults: DefaultsPreferences = field(default_factory=DefaultsPreferences)
|
|
137
|
+
export: ExportPreferences = field(default_factory=ExportPreferences)
|
|
138
|
+
logging: LoggingPreferences = field(default_factory=LoggingPreferences)
|
|
139
|
+
editor: EditorPreferences = field(default_factory=EditorPreferences)
|
|
140
|
+
recent_files: list[str] = field(default_factory=list)
|
|
141
|
+
custom: dict[str, Any] = field(default_factory=dict)
|
|
142
|
+
|
|
143
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
144
|
+
"""Get preference value by dot-notation key.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
key: Key path (e.g., "visualization.dpi")
|
|
148
|
+
default: Default value if not found
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Preference value
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> prefs.get("visualization.dpi")
|
|
155
|
+
100
|
|
156
|
+
>>> prefs.get("custom.my_setting", 42)
|
|
157
|
+
42
|
|
158
|
+
"""
|
|
159
|
+
parts = key.split(".")
|
|
160
|
+
obj: Any = self
|
|
161
|
+
|
|
162
|
+
for part in parts:
|
|
163
|
+
if isinstance(obj, dict):
|
|
164
|
+
obj = obj.get(part, default)
|
|
165
|
+
if obj is default:
|
|
166
|
+
return default
|
|
167
|
+
elif hasattr(obj, part):
|
|
168
|
+
obj = getattr(obj, part)
|
|
169
|
+
else:
|
|
170
|
+
return default
|
|
171
|
+
|
|
172
|
+
return obj
|
|
173
|
+
|
|
174
|
+
def set(self, key: str, value: Any) -> None:
|
|
175
|
+
"""Set preference value by dot-notation key.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
key: Key path (e.g., "visualization.dpi")
|
|
179
|
+
value: Value to set
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
KeyError: If preference path is invalid or unknown.
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
>>> prefs.set("visualization.dpi", 150)
|
|
186
|
+
>>> prefs.set("custom.my_setting", 42)
|
|
187
|
+
"""
|
|
188
|
+
parts = key.split(".")
|
|
189
|
+
|
|
190
|
+
if parts[0] == "custom":
|
|
191
|
+
# Custom preferences are a dict
|
|
192
|
+
if len(parts) == 2:
|
|
193
|
+
self.custom[parts[1]] = value
|
|
194
|
+
else:
|
|
195
|
+
# Nested custom preferences
|
|
196
|
+
current = self.custom
|
|
197
|
+
for part in parts[1:-1]:
|
|
198
|
+
if part not in current:
|
|
199
|
+
current[part] = {}
|
|
200
|
+
current = current[part]
|
|
201
|
+
current[parts[-1]] = value
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
# Navigate to parent
|
|
205
|
+
obj: Any = self
|
|
206
|
+
for part in parts[:-1]:
|
|
207
|
+
if hasattr(obj, part):
|
|
208
|
+
obj = getattr(obj, part)
|
|
209
|
+
else:
|
|
210
|
+
raise KeyError(f"Invalid preference path: {key}")
|
|
211
|
+
|
|
212
|
+
# Set value
|
|
213
|
+
final_part = parts[-1]
|
|
214
|
+
if hasattr(obj, final_part):
|
|
215
|
+
setattr(obj, final_part, value)
|
|
216
|
+
else:
|
|
217
|
+
raise KeyError(f"Unknown preference: {key}")
|
|
218
|
+
|
|
219
|
+
def to_dict(self) -> dict[str, Any]:
|
|
220
|
+
"""Convert preferences to dictionary.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dictionary representation
|
|
224
|
+
"""
|
|
225
|
+
return {
|
|
226
|
+
"visualization": {
|
|
227
|
+
"style": self.visualization.style,
|
|
228
|
+
"figure_size": list(self.visualization.figure_size),
|
|
229
|
+
"dpi": self.visualization.dpi,
|
|
230
|
+
"colormap": self.visualization.colormap,
|
|
231
|
+
"grid": self.visualization.grid,
|
|
232
|
+
"dark_mode": self.visualization.dark_mode,
|
|
233
|
+
},
|
|
234
|
+
"defaults": {
|
|
235
|
+
"sample_rate": self.defaults.sample_rate,
|
|
236
|
+
"window_function": self.defaults.window_function,
|
|
237
|
+
"fft_size": self.defaults.fft_size,
|
|
238
|
+
"rise_time_thresholds": list(self.defaults.rise_time_thresholds),
|
|
239
|
+
"logic_family": self.defaults.logic_family,
|
|
240
|
+
},
|
|
241
|
+
"export": {
|
|
242
|
+
"default_format": self.export.default_format,
|
|
243
|
+
"precision": self.export.precision,
|
|
244
|
+
"include_metadata": self.export.include_metadata,
|
|
245
|
+
"compression": self.export.compression,
|
|
246
|
+
},
|
|
247
|
+
"logging": {
|
|
248
|
+
"level": self.logging.level,
|
|
249
|
+
"file": self.logging.file,
|
|
250
|
+
"format": self.logging.format,
|
|
251
|
+
"show_timestamps": self.logging.show_timestamps,
|
|
252
|
+
},
|
|
253
|
+
"editor": {
|
|
254
|
+
"history_size": self.editor.history_size,
|
|
255
|
+
"auto_save": self.editor.auto_save,
|
|
256
|
+
"syntax_highlighting": self.editor.syntax_highlighting,
|
|
257
|
+
"tab_completion": self.editor.tab_completion,
|
|
258
|
+
},
|
|
259
|
+
"recent_files": self.recent_files,
|
|
260
|
+
"custom": self.custom,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def from_dict(cls, data: dict[str, Any]) -> UserPreferences:
|
|
265
|
+
"""Create preferences from dictionary.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
data: Dictionary representation
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
UserPreferences instance
|
|
272
|
+
"""
|
|
273
|
+
prefs = cls()
|
|
274
|
+
|
|
275
|
+
# Visualization
|
|
276
|
+
if "visualization" in data:
|
|
277
|
+
v = data["visualization"]
|
|
278
|
+
prefs.visualization = VisualizationPreferences(
|
|
279
|
+
style=v.get("style", prefs.visualization.style),
|
|
280
|
+
figure_size=tuple(v.get("figure_size", prefs.visualization.figure_size)),
|
|
281
|
+
dpi=v.get("dpi", prefs.visualization.dpi),
|
|
282
|
+
colormap=v.get("colormap", prefs.visualization.colormap),
|
|
283
|
+
grid=v.get("grid", prefs.visualization.grid),
|
|
284
|
+
dark_mode=v.get("dark_mode", prefs.visualization.dark_mode),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Defaults
|
|
288
|
+
if "defaults" in data:
|
|
289
|
+
d = data["defaults"]
|
|
290
|
+
prefs.defaults = DefaultsPreferences(
|
|
291
|
+
sample_rate=d.get("sample_rate", prefs.defaults.sample_rate),
|
|
292
|
+
window_function=d.get("window_function", prefs.defaults.window_function),
|
|
293
|
+
fft_size=d.get("fft_size", prefs.defaults.fft_size),
|
|
294
|
+
rise_time_thresholds=tuple(
|
|
295
|
+
d.get("rise_time_thresholds", prefs.defaults.rise_time_thresholds)
|
|
296
|
+
),
|
|
297
|
+
logic_family=d.get("logic_family", prefs.defaults.logic_family),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Export
|
|
301
|
+
if "export" in data:
|
|
302
|
+
e = data["export"]
|
|
303
|
+
prefs.export = ExportPreferences(
|
|
304
|
+
default_format=e.get("default_format", prefs.export.default_format),
|
|
305
|
+
precision=e.get("precision", prefs.export.precision),
|
|
306
|
+
include_metadata=e.get("include_metadata", prefs.export.include_metadata),
|
|
307
|
+
compression=e.get("compression", prefs.export.compression),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Logging
|
|
311
|
+
if "logging" in data:
|
|
312
|
+
lg = data["logging"]
|
|
313
|
+
prefs.logging = LoggingPreferences(
|
|
314
|
+
level=lg.get("level", prefs.logging.level),
|
|
315
|
+
file=lg.get("file", prefs.logging.file),
|
|
316
|
+
format=lg.get("format", prefs.logging.format),
|
|
317
|
+
show_timestamps=lg.get("show_timestamps", prefs.logging.show_timestamps),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Editor
|
|
321
|
+
if "editor" in data:
|
|
322
|
+
ed = data["editor"]
|
|
323
|
+
prefs.editor = EditorPreferences(
|
|
324
|
+
history_size=ed.get("history_size", prefs.editor.history_size),
|
|
325
|
+
auto_save=ed.get("auto_save", prefs.editor.auto_save),
|
|
326
|
+
syntax_highlighting=ed.get("syntax_highlighting", prefs.editor.syntax_highlighting),
|
|
327
|
+
tab_completion=ed.get("tab_completion", prefs.editor.tab_completion),
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
prefs.recent_files = data.get("recent_files", [])
|
|
331
|
+
prefs.custom = data.get("custom", {})
|
|
332
|
+
|
|
333
|
+
return prefs
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class PreferencesManager:
|
|
337
|
+
"""Manager for loading and saving user preferences.
|
|
338
|
+
|
|
339
|
+
Handles preferences file location, loading, saving, and migration.
|
|
340
|
+
|
|
341
|
+
Example:
|
|
342
|
+
>>> manager = PreferencesManager()
|
|
343
|
+
>>> prefs = manager.load()
|
|
344
|
+
>>> prefs.visualization.dark_mode = True
|
|
345
|
+
>>> manager.save(prefs)
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
def __init__(self, path: Path | None = None):
|
|
349
|
+
"""Initialize preferences manager.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
path: Override preferences file path
|
|
353
|
+
"""
|
|
354
|
+
self._path = path or self._get_default_path()
|
|
355
|
+
self._cached: UserPreferences | None = None
|
|
356
|
+
|
|
357
|
+
def _get_default_path(self) -> Path:
|
|
358
|
+
"""Get default preferences file path."""
|
|
359
|
+
xdg_config = os.environ.get("XDG_CONFIG_HOME")
|
|
360
|
+
base = Path(xdg_config) if xdg_config else Path.home() / ".config"
|
|
361
|
+
|
|
362
|
+
config_dir = base / "oscura"
|
|
363
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
364
|
+
return config_dir / "preferences.yaml"
|
|
365
|
+
|
|
366
|
+
def load(self, use_cache: bool = True) -> UserPreferences:
|
|
367
|
+
"""Load preferences from file.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
use_cache: Use cached preferences if available
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
User preferences
|
|
374
|
+
"""
|
|
375
|
+
if use_cache and self._cached is not None:
|
|
376
|
+
return self._cached
|
|
377
|
+
|
|
378
|
+
if not self._path.exists():
|
|
379
|
+
logger.debug(f"No preferences file at {self._path}, using defaults")
|
|
380
|
+
self._cached = UserPreferences()
|
|
381
|
+
return self._cached
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
with open(self._path, encoding="utf-8") as f:
|
|
385
|
+
data = yaml.safe_load(f)
|
|
386
|
+
|
|
387
|
+
if data is None:
|
|
388
|
+
data = {}
|
|
389
|
+
|
|
390
|
+
# Validate against schema if available
|
|
391
|
+
try:
|
|
392
|
+
validate_against_schema(data, "preferences")
|
|
393
|
+
except Exception as e:
|
|
394
|
+
logger.warning(f"Preferences validation warning: {e}")
|
|
395
|
+
|
|
396
|
+
self._cached = UserPreferences.from_dict(data)
|
|
397
|
+
logger.debug(f"Loaded preferences from {self._path}")
|
|
398
|
+
return self._cached
|
|
399
|
+
|
|
400
|
+
except Exception as e:
|
|
401
|
+
logger.warning(f"Failed to load preferences from {self._path}: {e}")
|
|
402
|
+
self._cached = UserPreferences()
|
|
403
|
+
return self._cached
|
|
404
|
+
|
|
405
|
+
def save(self, prefs: UserPreferences | None = None) -> None:
|
|
406
|
+
"""Save preferences to file.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
prefs: Preferences to save (uses cached if None)
|
|
410
|
+
|
|
411
|
+
Raises:
|
|
412
|
+
ConfigurationError: If saving to file fails.
|
|
413
|
+
"""
|
|
414
|
+
prefs = prefs or self._cached
|
|
415
|
+
if prefs is None:
|
|
416
|
+
prefs = UserPreferences()
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
data = prefs.to_dict()
|
|
420
|
+
|
|
421
|
+
# Ensure parent directory exists
|
|
422
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
423
|
+
|
|
424
|
+
with open(self._path, "w", encoding="utf-8") as f:
|
|
425
|
+
yaml.dump(data, f, default_flow_style=False)
|
|
426
|
+
|
|
427
|
+
self._cached = prefs
|
|
428
|
+
logger.debug(f"Saved preferences to {self._path}")
|
|
429
|
+
|
|
430
|
+
except Exception as e:
|
|
431
|
+
logger.error(f"Failed to save preferences to {self._path}: {e}")
|
|
432
|
+
raise ConfigurationError(f"Failed to save preferences to {self._path}: {e}") from e
|
|
433
|
+
|
|
434
|
+
def reset(self) -> UserPreferences:
|
|
435
|
+
"""Reset preferences to defaults.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Default preferences
|
|
439
|
+
"""
|
|
440
|
+
self._cached = UserPreferences()
|
|
441
|
+
self.save(self._cached)
|
|
442
|
+
logger.info("Reset preferences to defaults")
|
|
443
|
+
return self._cached
|
|
444
|
+
|
|
445
|
+
def add_recent_file(self, path: str | Path, max_recent: int = 10) -> None:
|
|
446
|
+
"""Add file to recent files list.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
path: File path
|
|
450
|
+
max_recent: Maximum number of recent files to keep
|
|
451
|
+
"""
|
|
452
|
+
prefs = self.load()
|
|
453
|
+
path_str = str(path)
|
|
454
|
+
|
|
455
|
+
# Remove if already in list
|
|
456
|
+
if path_str in prefs.recent_files:
|
|
457
|
+
prefs.recent_files.remove(path_str)
|
|
458
|
+
|
|
459
|
+
# Add to front
|
|
460
|
+
prefs.recent_files.insert(0, path_str)
|
|
461
|
+
|
|
462
|
+
# Trim to max
|
|
463
|
+
prefs.recent_files = prefs.recent_files[:max_recent]
|
|
464
|
+
|
|
465
|
+
self.save(prefs)
|
|
466
|
+
|
|
467
|
+
def get_recent_files(self, max_count: int = 10) -> list[str]:
|
|
468
|
+
"""Get list of recent files.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
max_count: Maximum number to return
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
List of recent file paths
|
|
475
|
+
"""
|
|
476
|
+
prefs = self.load()
|
|
477
|
+
return prefs.recent_files[:max_count]
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def path(self) -> Path:
|
|
481
|
+
"""Get preferences file path."""
|
|
482
|
+
return self._path
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# Global preferences manager
|
|
486
|
+
_manager: PreferencesManager | None = None
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def get_preferences_manager() -> PreferencesManager:
|
|
490
|
+
"""Get global preferences manager.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Global PreferencesManager instance
|
|
494
|
+
"""
|
|
495
|
+
global _manager
|
|
496
|
+
if _manager is None:
|
|
497
|
+
_manager = PreferencesManager()
|
|
498
|
+
return _manager
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def get_preferences() -> UserPreferences:
|
|
502
|
+
"""Get current user preferences.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
User preferences
|
|
506
|
+
"""
|
|
507
|
+
return get_preferences_manager().load()
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def save_preferences(prefs: UserPreferences | None = None) -> None:
|
|
511
|
+
"""Save user preferences.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
prefs: Preferences to save
|
|
515
|
+
"""
|
|
516
|
+
get_preferences_manager().save(prefs)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
__all__ = [
|
|
520
|
+
"DefaultsPreferences",
|
|
521
|
+
"EditorPreferences",
|
|
522
|
+
"ExportPreferences",
|
|
523
|
+
"LoggingPreferences",
|
|
524
|
+
"PreferencesManager",
|
|
525
|
+
"UserPreferences",
|
|
526
|
+
"VisualizationPreferences",
|
|
527
|
+
"get_preferences",
|
|
528
|
+
"get_preferences_manager",
|
|
529
|
+
"save_preferences",
|
|
530
|
+
]
|