CustomSettingsPanelProvider - Enriched API Documentation
Executive Summaryβ
CustomSettingsPanelProvider is an interface in the Bookmap Simplified API that enables add-ons to create custom GUI panels for user configuration. It provides full control over the settings UI, as an alternative to the automatic UI generation provided by the @Parameter annotation. The interface is designed for scenarios requiring complex layouts, dynamic controls, real-time updates, or specialized input components.
Table of Contentsβ
- Interface Overview
- Method Reference
- Comparison with @Parameter Annotation
- Related Classes
- Implementation Patterns
- Common UI Components
- Settings Persistence
- Disabled State UI
- Complete Examples
- Edge Cases and Gotchas
- Best Practices
- Document Metadata
Interface Overviewβ
Package and Declarationβ
package velox.api.layer1.simplified;
public interface CustomSettingsPanelProvider
Purpose: Allows specifying custom UI directly for module configuration.
Official Descriptionβ
Allows specifying custom UI directly. Alternatively you can use
Parameterfor automatic UI generation.This UI is only shown when module is enabled. If you want to show some placeholder (usually a disabled version of UI with default values) when module is disabled - implement the following method:
public static StrategyPanel[] getCustomDisabledSettingsPanels()This functionality is, strictly speaking, independent of this interface, but you will typically want to use both together. Important: If you use an obfuscator make sure to exclude this method.
Key Characteristicsβ
- Single-method interface (functional interface candidate)
- Returns array of
StrategyPanelobjects - Panels displayed in Bookmap's strategy dialog
- Only shown when module is enabled
- Complementary static method for disabled state UI
When to Useβ
| Scenario | Use CustomSettingsPanelProvider | Use @Parameter |
|---|---|---|
| Simple numeric/boolean/color settings | Γ’ΒΕ Overkill | Γ’Εβ¦ Recommended |
| Complex layouts with multiple sections | Γ’Εβ¦ Required | Γ’ΒΕ Not possible |
| Dynamic UI (add/remove rows) | Γ’Εβ¦ Required | Γ’ΒΕ Not possible |
| Real-time statistics display | Γ’Εβ¦ Required | Γ’ΒΕ Not possible |
| Custom input components (sliders, charts) | Γ’Εβ¦ Required | Γ’ΒΕ Not possible |
| Color pickers with preview | Γ’Εβ¦ Recommended | Γ’Ε‘Β Γ―ΒΈΒ Basic only |
| Interdependent settings | Γ’Εβ¦ Recommended | Γ’ΒΕ Difficult |
Method Referenceβ
getCustomSettingsPanels()β
velox.gui.StrategyPanel[] getCustomSettingsPanels()
Returns: Array of StrategyPanel objects to display in the strategy configuration dialog.
Description: Called by Bookmap when the user opens the configuration dialog for an enabled module. Each panel appears as a collapsible section with a title.
Return Value Details:
- First panel in array appears topmost in the dialog
- Panels are displayed in array order
- Empty array
new StrategyPanel[0]is valid (no custom UI) nullreturn is not recommended (may cause issues)
When Called:
- When user clicks the settings button for the module
- When
StrategyPanel.requestReload()is called - After
api.reload()completes (new instance created)
Threading:
- Called on the Swing Event Dispatch Thread (EDT)
- UI components must be created/modified on EDT
- Long-running operations should use background threads
Confidence: HIGH (directly documented, verified in examples)
Comparison with @Parameter Annotationβ
@Parameter Annotation (Automatic UI)β
@Parameter(name = "Line Width", minimum = 1, maximum = 10, step = 1)
public int lineWidth = 3;
@Parameter(name = "Show Indicator")
public boolean showIndicator = true;
@Parameter(name = "Indicator Color")
public Color indicatorColor = Color.GREEN;
Supported Types:
Byte,Short,Integer,Long,Float,DoubleΓ’β β SpinnerStringΓ’β β Text fieldBooleanΓ’β β CheckboxColorΓ’β β Color picker
Limitations:
- Fixed layout (label + control per row)
- No grouping or sections
- No dynamic behavior
- No custom validation
- No real-time preview
CustomSettingsPanelProvider (Manual UI)β
@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel p1 = new StrategyPanel("Line Settings");
JSpinner widthSpinner = new JSpinner(new SpinnerNumberModel(lineWidth, 1, 10, 1));
widthSpinner.addChangeListener(e -> {
lineWidth = (Integer) widthSpinner.getValue();
indicator.setWidth(lineWidth); // Real-time update!
});
p1.setLayout(new BorderLayout());
p1.add(new JLabel("Line Width:"), BorderLayout.WEST);
p1.add(widthSpinner, BorderLayout.CENTER);
return new StrategyPanel[] { p1 };
}
Advantages:
- Full layout control (BorderLayout, GridBagLayout, etc.)
- Multiple panels with logical grouping
- Dynamic UI elements
- Real-time indicator updates
- Custom validation and feedback
- Complex interdependencies
Related Classesβ
StrategyPanelβ
velox.gui.StrategyPanel extends JPanel
Purpose: Container for settings UI with automatic styling.
Key Features:
- Automatic border and title styling
- Built-in enabled/disabled state management
- Reload request capability
- Localization support
Constructors:
StrategyPanel(String title)
StrategyPanel(String title, LayoutManager layout)
StrategyPanel(String title, boolean isDoubleBuffered)
StrategyPanel(String title, LayoutManager layout, boolean isDoubleBuffered)
Key Methods:
void setEnabled(boolean enabled) // Enable/disable all child components
void requestReload() // Trigger panel refresh
void addReloadListener(Runnable r) // Listen for reload requests
String getTitle() // Get panel title
void setLocalizedTitle(TranslatableComponent title) // Set localized title
Styling Behavior:
title != nullΓ’β β Displays with styled border and titletitle == nullΓ’β β No border, no title (plain panel)
SimpleStrategyPanelβ
velox.api.layer1.simplified.SimpleStrategyPanel extends StrategyPanel
Purpose: Simplified panel with grid-based layout helpers.
Key Methods:
void addItem(String label, Component c) // Label + component row
void addItems(String label, Component... c) // Label + multiple components
void addItems(Component... components) // Components only
void addItems(Component[] c, int[] gridWidths) // Custom grid widths
Example:
SimpleStrategyPanel panel = new SimpleStrategyPanel("Settings");
panel.addItem("Line Width:", new JSpinner(new SpinnerNumberModel(3, 1, 10, 1)));
panel.addItem("Color:", new ColorsConfigItem(Color.RED, Color.RED, c -> {}));
ColorsConfigItemβ
velox.gui.colors.ColorsConfigItem extends JPanel
Purpose: Bookmap-styled color picker component.
Key Constructors:
// Simple version (most common)
ColorsConfigItem(Color currentColor, Color defaultColor, Consumer<Color> colorChangedListener)
// With label
ColorsConfigItem(Color currentColor, Color defaultColor, String label, Consumer<Color> colorChangedListener)
Features:
- Matches Bookmap's native color picker styling
- Reset-to-default button
- Color preview
- Callback on color change (including reset)
Example:
ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.lineColor, // Current color
Color.GREEN, // Default color
"Line Color", // Label (optional)
color -> {
settings.lineColor = color;
indicator.setColor(color);
api.setSettings(settings);
}
);
Important Note:
ColorsConfigItem currently can trigger callback during construction
This means you should guard against null api references in the callback:
ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.lineColor, Color.GREEN, color -> {
settings.lineColor = color;
if (api != null) { // Guard against construction-time callback
api.setSettings(settings);
indicator.setColor(color);
}
}
);
GuiUtilsβ
velox.gui.utils.GuiUtils
Purpose: Utility methods for GUI manipulation.
Key Method:
static void setPanelEnabled(JPanel panel, Boolean isEnabled)
Recursively enables/disables all components within a panel. Essential for creating disabled state UI.
Example:
public static StrategyPanel[] getCustomDisabledSettingsPanels() {
StrategyPanel[] panels = createPanels(new Settings(), null, null);
for (StrategyPanel panel : panels) {
GuiUtils.setPanelEnabled(panel, false);
}
return panels;
}
Implementation Patternsβ
Pattern 1: Basic Single Panelβ
@Layer1SimpleAttachable
@Layer1StrategyName("Basic Settings Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class BasicSettingsDemo implements CustomModule, CustomSettingsPanelProvider {
private Api api;
private int threshold = 10;
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel panel = new StrategyPanel("Settings");
panel.setLayout(new BorderLayout());
JSpinner spinner = new JSpinner(new SpinnerNumberModel(threshold, 1, 100, 1));
spinner.addChangeListener(e -> {
threshold = (Integer) spinner.getValue();
});
panel.add(new JLabel("Threshold:"), BorderLayout.WEST);
panel.add(spinner, BorderLayout.CENTER);
return new StrategyPanel[] { panel };
}
@Override
public void stop() {}
}
Pattern 2: Multiple Panels with Settings Persistenceβ
@Layer1SimpleAttachable
@Layer1StrategyName("Multi-Panel Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class MultiPanelDemo implements CustomModule, CustomSettingsPanelProvider {
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
public int lineWidth = 3;
public Color lineColor = Color.GREEN;
public boolean showWidget = true;
}
private Api api;
private Settings settings;
private IndicatorModifiable indicator;
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.settings = api.getSettings(Settings.class);
indicator = api.registerIndicatorModifiable("Demo", GraphType.BOTTOM);
indicator.setWidth(settings.lineWidth);
indicator.setColor(settings.lineColor);
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
// Panel 1: Visual Settings
StrategyPanel visualPanel = new StrategyPanel("Visual Settings");
visualPanel.setLayout(new BorderLayout(5, 5));
JSpinner widthSpinner = new JSpinner(new SpinnerNumberModel(settings.lineWidth, 1, 10, 1));
widthSpinner.addChangeListener(e -> {
settings.lineWidth = (Integer) widthSpinner.getValue();
api.setSettings(settings);
indicator.setWidth(settings.lineWidth);
});
visualPanel.add(new JLabel("Line Width:"), BorderLayout.WEST);
visualPanel.add(widthSpinner, BorderLayout.CENTER);
// Panel 2: Color Settings
StrategyPanel colorPanel = new StrategyPanel("Color Settings");
colorPanel.setLayout(new BorderLayout());
ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.lineColor, Color.GREEN, color -> {
settings.lineColor = color;
if (api != null) {
api.setSettings(settings);
indicator.setColor(color);
}
}
);
colorPanel.add(colorPicker, BorderLayout.CENTER);
return new StrategyPanel[] { visualPanel, colorPanel };
}
@Override
public void stop() {}
}
Pattern 3: Dynamic UI with Add/Remove Rowsβ
Based on StopLossRandomizerSettings.java:
@Layer1SimpleAttachable
@Layer1StrategyName("Dynamic Rows Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class DynamicRowsDemo implements CustomModule, CustomSettingsPanelProvider {
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
public int numRows = 3;
}
private Api api;
private Settings settings;
private StrategyPanel panel;
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.settings = api.getSettings(Settings.class);
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
updatePanel();
return new StrategyPanel[] { panel };
}
private void updatePanel() {
panel = new StrategyPanel("Dynamic Settings");
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 2, 2, 2);
// Header row
gbc.gridy = 0;
gbc.gridx = 0; panel.add(new JLabel("Column A"), gbc);
gbc.gridx = 1; panel.add(new JLabel("Column B"), gbc);
gbc.gridx = 2; panel.add(new JLabel("Column C"), gbc);
// Dynamic rows
for (int i = 0; i < settings.numRows; i++) {
gbc.gridy = i + 1;
gbc.gridx = 0; panel.add(new JComboBox<>(new Integer[]{1, 2, 3, 4, 5}), gbc);
gbc.gridx = 1; panel.add(new JComboBox<>(new Integer[]{1, 2, 3, 4, 5}), gbc);
gbc.gridx = 2; panel.add(new JComboBox<>(new Integer[]{1, 2, 3, 4, 5}), gbc);
}
// Add button
gbc.gridy = settings.numRows + 1;
gbc.gridx = 2;
JButton addButton = new JButton("Add Row");
addButton.addActionListener(e -> {
settings.numRows++;
updatePanel();
// Force UI refresh
panel.getParent().revalidate();
panel.getParent().repaint();
});
panel.add(addButton, gbc);
}
@Override
public void stop() {
api.setSettings(settings);
}
}
Pattern 4: Real-Time Statistics Displayβ
Based on AtrTrailingStopSettings.java:
@Override
public StrategyPanel[] getCustomSettingsPanels() {
// ... parameter panels ...
// Statistics panel with live-updating labels
StrategyPanel statsPanel = new StrategyPanel("Current Values");
statsPanel.setLayout(new GridLayout(2, 2, 5, 5));
statsPanel.add(new JLabel("Last True Range:"));
statsPanel.add(labelTr); // Updated by onAtrUpdated()
statsPanel.add(new JLabel("Average True Range:"));
statsPanel.add(labelAtr); // Updated by onAtrUpdated()
return new StrategyPanel[] { parametersPanel, statsPanel };
}
// Called from data processing logic
protected void onAtrUpdated(double tr, double atr) {
// Update labels on EDT
SwingUtilities.invokeLater(() -> {
labelTr.setText(String.format("%.3f", tr));
labelAtr.setText(String.format("%.3f", atr));
});
}
Pattern 5: Reload Button for Settings Changesβ
Based on SettingsAndUiDemo.java:
@Override
public StrategyPanel[] getCustomSettingsPanels() {
StrategyPanel panel = new StrategyPanel("Offset Settings");
panel.setLayout(new BorderLayout());
JSpinner offsetSpinner = new JSpinner(new SpinnerNumberModel(
settings.lastTradeOffset, -100, 100, 1));
offsetSpinner.addChangeListener(e -> {
settings.lastTradeOffset = (Integer) offsetSpinner.getValue();
api.setSettings(settings);
// Note: This affects new values only, not computed historical data
});
// Reload button for full recalculation
JButton reloadButton = new JButton("Apply and Reload");
reloadButton.addActionListener(e -> api.reload());
panel.add(offsetSpinner, BorderLayout.CENTER);
panel.add(reloadButton, BorderLayout.EAST);
return new StrategyPanel[] { panel };
}
Common UI Componentsβ
JSpinner (Numeric Input)β
JSpinner spinner = new JSpinner(new SpinnerNumberModel(
initialValue, // Current value
minimum, // Minimum allowed
maximum, // Maximum allowed
step // Step increment
));
spinner.addChangeListener(e -> {
int value = (Integer) spinner.getValue();
// Handle change
});
JComboBox (Dropdown Selection)β
// With enum values
JComboBox<LineStyle> styleCombo = new JComboBox<>(LineStyle.values());
styleCombo.setSelectedItem(settings.lineStyle);
styleCombo.addActionListener(e -> {
settings.lineStyle = (LineStyle) styleCombo.getSelectedItem();
});
// With custom objects
JComboBox<Integer> intCombo = new JComboBox<>(new Integer[]{1, 5, 10, 15, 30, 60});
intCombo.setSelectedItem(settings.interval);
intCombo.addActionListener(e -> {
settings.interval = (Integer) intCombo.getSelectedItem();
});
// Alignment fix (common pattern from examples)
((JLabel) comboBox.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
JButton (Actions)β
JButton resetButton = new JButton("Restore Defaults");
resetButton.addActionListener(e -> {
settings = new Settings(); // Reset to defaults
api.setSettings(settings);
api.reload(); // Reload to apply
});
JLabel (Display Only)β
JLabel statusLabel = new JLabel("Ready");
// Update from processing thread:
SwingUtilities.invokeLater(() -> statusLabel.setText("Processing..."));
ColorsConfigItem (Color Picker)β
ColorsConfigItem colorPicker = new ColorsConfigItem(
currentColor,
defaultColor,
"Label Text", // Optional, can be null
color -> {
// Handle color change
if (api != null) {
settings.color = color;
api.setSettings(settings);
indicator.setColor(color);
}
}
);
Settings Persistenceβ
Settings Class Requirementsβ
- Annotate with
@StrategySettingsVersion:
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
// Fields with default values
}
-
All fields must be serializable (primitives, String, Color, enums, etc.)
-
Provide default values for all fields
Loading Settingsβ
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
// Load saved settings (or create new with defaults if none saved)
this.settings = api.getSettings(Settings.class);
}
Saving Settingsβ
// After any settings change
private void onSettingChanged() {
api.setSettings(settings); // Persist immediately
}
// Or in stop() for batch save
@Override
public void stop() {
api.setSettings(settings);
}
Version Migrationβ
@StrategySettingsVersion(currentVersion = 2, compatibleVersions = {1})
public static class Settings {
// v1 fields
public int oldField = 10;
// v2 new fields (must have defaults for migration)
public int newField = 20;
}
If stored settings version is not in compatibleVersions, default settings are used.
Disabled State UIβ
Purposeβ
When a module is not loaded (disabled), Bookmap can still display a placeholder UI showing default settings. This helps users understand what the module does before enabling it.
Implementationβ
/**
* Optional static method for disabled state UI.
* IMPORTANT: If using an obfuscator, exclude this method!
*/
public static StrategyPanel[] getCustomDisabledSettingsPanels() {
// Create panels with default settings
Settings defaultSettings = new Settings();
StrategyPanel[] panels = createPanels(defaultSettings, null, null);
// Disable all components
for (StrategyPanel panel : panels) {
GuiUtils.setPanelEnabled(panel, false);
}
return panels;
}
// Reusable panel creation (used by both enabled and disabled states)
private static StrategyPanel[] createPanels(Settings settings, Api api, Indicator indicator) {
StrategyPanel panel = new StrategyPanel("Settings");
JSpinner spinner = new JSpinner(new SpinnerNumberModel(settings.lineWidth, 1, 10, 1));
spinner.addChangeListener(e -> {
if (api != null) { // Only respond when enabled
settings.lineWidth = (Integer) spinner.getValue();
api.setSettings(settings);
indicator.setWidth(settings.lineWidth);
}
});
panel.setLayout(new BorderLayout());
panel.add(spinner, BorderLayout.CENTER);
return new StrategyPanel[] { panel };
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
// Reuse same panel creation logic
return createPanels(settings, api, indicator);
}
Key Pointsβ
- Static method - No instance required
- Standard name - Must be exactly
getCustomDisabledSettingsPanels - Returns
StrategyPanel[]- Same return type as instance method - Use
GuiUtils.setPanelEnabled(panel, false)- Disables all child components - Obfuscator exclusion - Method name must not be obfuscated
Complete Examplesβ
Example 1: Settings/UI Demo (From Official Examples)β
Based on SettingsAndUiDemo.java:
package velox.api.layer1.simplified.demo;
import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.*;
import velox.api.layer1.annotations.*;
import velox.api.layer1.data.InstrumentInfo;
import velox.api.layer1.data.TradeInfo;
import velox.api.layer1.messages.indicators.Layer1ApiUserMessageModifyIndicator.GraphType;
import velox.api.layer1.settings.StrategySettingsVersion;
import velox.api.layer1.simplified.*;
import velox.gui.StrategyPanel;
import velox.gui.colors.ColorsConfigItem;
import velox.gui.utils.GuiUtils;
@Layer1SimpleAttachable
@Layer1StrategyName("Settings/UI demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class SettingsAndUiDemo implements
CustomModule,
TradeDataListener,
CustomSettingsPanelProvider
{
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public static class Settings {
int lastTradeOffset = 0;
Color lastTradeColor = DEFAULT_LAST_TRADE_COLOR;
int lineWidth = 3;
LineStyle lineStyle = LineStyle.SOLID;
}
private static final Color DEFAULT_LAST_TRADE_COLOR = Color.GREEN;
private Api api;
private Indicator lastTradeIndicator;
private Settings settings;
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
lastTradeIndicator = api.registerIndicator("Last trade, no history", GraphType.PRIMARY);
settings = api.getSettings(Settings.class);
lastTradeIndicator.setColor(settings.lastTradeColor);
lastTradeIndicator.setLineStyle(settings.lineStyle);
lastTradeIndicator.setWidth(settings.lineWidth);
}
@Override
public void stop() {}
@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
lastTradeIndicator.addPoint(price + settings.lastTradeOffset);
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
return getCustomSettingsPanels(settings, api, lastTradeIndicator);
}
/**
* Generate custom settings UI. Since we want to generate both disabled and
* enabled UI we move the code to separate method.
*/
private static StrategyPanel[] getCustomSettingsPanels(
Settings settings, Api api, Indicator lastTradeIndicator) {
// Panel 1: Offset with reload button
StrategyPanel p1 = new StrategyPanel("Last trade offset");
JSpinner offsetSpinner = new JSpinner(new SpinnerNumberModel(
settings.lastTradeOffset, -100, 100, 1));
offsetSpinner.addChangeListener(e -> {
settings.lastTradeOffset = (Integer) offsetSpinner.getValue();
api.setSettings(settings);
});
JButton reloadButton = new JButton("Apply and reload");
reloadButton.addActionListener(e -> api.reload());
p1.setLayout(new BorderLayout());
p1.add(offsetSpinner, BorderLayout.CENTER);
p1.add(reloadButton, BorderLayout.EAST);
// Panel 2: Color picker
StrategyPanel p2 = new StrategyPanel("Line color");
ColorsConfigItem colorConfig = new ColorsConfigItem(
settings.lastTradeColor, DEFAULT_LAST_TRADE_COLOR, "Last trade",
color -> {
settings.lastTradeColor = color;
if (api != null) {
api.setSettings(settings);
lastTradeIndicator.setColor(color);
}
});
p2.setLayout(new BorderLayout());
p2.add(colorConfig, BorderLayout.CENTER);
// Panel 3: Line width
StrategyPanel p3 = new StrategyPanel("Line width");
JSpinner lineWidthSpinner = new JSpinner(new SpinnerNumberModel(
settings.lineWidth, 1, 10, 1));
lineWidthSpinner.addChangeListener(e -> {
settings.lineWidth = (Integer) lineWidthSpinner.getValue();
api.setSettings(settings);
lastTradeIndicator.setWidth(settings.lineWidth);
});
p3.setLayout(new BorderLayout());
p3.add(lineWidthSpinner, BorderLayout.CENTER);
// Panel 4: Line style
StrategyPanel p4 = new StrategyPanel("Line style");
JComboBox<LineStyle> lineStyleComboBox = new JComboBox<>(LineStyle.values());
lineStyleComboBox.setSelectedItem(settings.lineStyle);
lineStyleComboBox.addActionListener(e -> {
settings.lineStyle = (LineStyle) lineStyleComboBox.getSelectedItem();
api.setSettings(settings);
lastTradeIndicator.setLineStyle(settings.lineStyle);
});
p4.setLayout(new BorderLayout());
p4.add(lineStyleComboBox, BorderLayout.CENTER);
StrategyPanel[] strategyPanels = new StrategyPanel[] {p1, p2, p3, p4};
// If module is not loaded - disable all components
if (api == null) {
for (StrategyPanel strategyPanel : strategyPanels) {
GuiUtils.setPanelEnabled(strategyPanel, false);
}
}
return strategyPanels;
}
/**
* Optional method generating UI for unloaded module.
*/
public static StrategyPanel[] getCustomDisabledSettingsPanels() {
return getCustomSettingsPanels(new Settings(), null, null);
}
}
Example 2: MBO Visualizer (Real-Time Display)β
Based on MboVisualizerNoHistory.java:
@Layer1SimpleAttachable
@Layer1StrategyName("Mbo visualizer: no history")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class MboVisualizerNoHistory implements
CustomModule,
CustomSettingsPanelProvider,
MarketByOrderDepthDataListener {
private OrderBookMbo orderBookMbo = new OrderBookMbo();
private OrderBook orderBook = new OrderBook();
private JLabel displayLabel;
private AtomicBoolean updateIsScheduled = new AtomicBoolean();
public MboVisualizerNoHistory() {
// Initialize label on EDT
SwingUtilities.invokeLater(() -> {
displayLabel = new JLabel();
});
}
@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {}
@Override
public void stop() {}
@Override
public void send(String orderId, boolean isBid, int price, int size) {
orderBookMbo.send(orderId, isBid, price, size);
synchronized (orderBook) {
long levelSize = orderBook.getSizeFor(isBid, price, 0) + size;
orderBook.onUpdate(isBid, price, levelSize);
}
scheduleUpdateIfNecessary();
}
@Override
public void replace(String orderId, int price, int size) {
// ... update logic ...
scheduleUpdateIfNecessary();
}
@Override
public void cancel(String orderId) {
// ... cancel logic ...
scheduleUpdateIfNecessary();
}
/**
* Throttled UI update - prevents flooding EDT with updates
*/
private void scheduleUpdateIfNecessary() {
boolean shouldSchedule = !updateIsScheduled.getAndSet(true);
if (shouldSchedule) {
SwingUtilities.invokeLater(() -> {
updateIsScheduled.set(false);
StringBuilder builder = new StringBuilder();
builder.append("<html>");
synchronized (orderBook) {
// Build display string from order book
// ...
}
builder.append("</html>");
displayLabel.setText(builder.toString());
});
}
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
// Reinitialize label for fresh panel
displayLabel = new JLabel();
scheduleUpdateIfNecessary();
StrategyPanel ordersPanel = new StrategyPanel("Order book");
ordersPanel.add(displayLabel);
return new StrategyPanel[] { ordersPanel };
}
}
Edge Cases and Gotchasβ
1. ColorsConfigItem Callback During Constructionβ
Problem:
ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.color, Color.GREEN, color -> {
settings.color = color;
api.setSettings(settings); // NPE if api is null!
indicator.setColor(color); // NPE if indicator is null!
}
);
Solution:
ColorsConfigItem colorPicker = new ColorsConfigItem(
settings.color, Color.GREEN, color -> {
settings.color = color;
if (api != null) { // Guard against construction-time callback
api.setSettings(settings);
indicator.setColor(color);
}
}
);
2. UI Updates from Non-EDT Threadβ
Problem:
// Called from data processing thread
public void onTrade(double price, int size, TradeInfo tradeInfo) {
statusLabel.setText("Trade: " + size); // Thread violation!
}
Solution:
public void onTrade(double price, int size, TradeInfo tradeInfo) {
SwingUtilities.invokeLater(() -> {
statusLabel.setText("Trade: " + size);
});
}
3. Panel Refresh After Dynamic Changesβ
Problem:
JButton addButton = new JButton("Add Row");
addButton.addActionListener(e -> {
settings.numRows++;
updatePanel(); // Panel rebuilt, but UI not refreshed
});
Solution:
addButton.addActionListener(e -> {
settings.numRows++;
updatePanel();
panel.getParent().revalidate(); // Force layout recalculation
panel.getParent().repaint(); // Force repaint
});
4. Settings Not Persistedβ
Problem:
spinner.addChangeListener(e -> {
settings.value = (Integer) spinner.getValue();
// Forgot to call api.setSettings()!
});
Solution:
spinner.addChangeListener(e -> {
settings.value = (Integer) spinner.getValue();
api.setSettings(settings); // Persist immediately
});
5. Obfuscator Breaking Disabled UIβ
Problem: Method getCustomDisabledSettingsPanels is renamed by obfuscator.
Solution: Configure obfuscator to exclude this method:
# ProGuard example
-keepclassmembers class * implements velox.api.layer1.simplified.CustomSettingsPanelProvider {
public static velox.gui.StrategyPanel[] getCustomDisabledSettingsPanels();
}
6. JComboBox Text Alignmentβ
Problem: JComboBox text appears centered, inconsistent with Bookmap style.
Solution:
JComboBox<String> combo = new JComboBox<>(options);
((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
7. Null Return from getCustomSettingsPanels()β
Problem:
@Override
public StrategyPanel[] getCustomSettingsPanels() {
return null; // May cause issues!
}
Solution:
@Override
public StrategyPanel[] getCustomSettingsPanels() {
return new StrategyPanel[0]; // Empty array if no panels needed
}
Best Practicesβ
1. Separate Panel Creation Logicβ
Extract panel creation to a static method usable by both enabled and disabled states:
private static StrategyPanel[] createPanels(Settings settings, Api api, Indicator indicator) {
// ... create panels ...
return panels;
}
@Override
public StrategyPanel[] getCustomSettingsPanels() {
return createPanels(settings, api, indicator);
}
public static StrategyPanel[] getCustomDisabledSettingsPanels() {
StrategyPanel[] panels = createPanels(new Settings(), null, null);
for (StrategyPanel p : panels) {
GuiUtils.setPanelEnabled(p, false);
}
return panels;
}
2. Real-Time Updates Without Reloadβ
For visual changes that don't require recalculation:
// Good: Immediate visual update
colorPicker.addActionListener(e -> {
settings.color = colorPicker.getColor();
api.setSettings(settings);
indicator.setColor(settings.color); // Immediate update
});
// Avoid: Unnecessary reload
colorPicker.addActionListener(e -> {
settings.color = colorPicker.getColor();
api.setSettings(settings);
api.reload(); // Overkill for color change
});
3. Provide Reload Button for Computed Valuesβ
For settings that affect historical calculations:
JButton reloadButton = new JButton("Apply and Reload");
reloadButton.setToolTipText("Recalculates all historical data with new settings");
reloadButton.addActionListener(e -> api.reload());
4. Use SimpleStrategyPanel for Grid Layoutsβ
SimpleStrategyPanel panel = new SimpleStrategyPanel("Settings");
panel.addItem("Line Width:", widthSpinner);
panel.addItem("Color:", colorPicker);
panel.addItem("Style:", styleCombo);
5. Throttle Real-Time Display Updatesβ
private AtomicBoolean updateScheduled = new AtomicBoolean(false);
private void requestDisplayUpdate() {
if (!updateScheduled.getAndSet(true)) {
SwingUtilities.invokeLater(() -> {
updateScheduled.set(false);
refreshDisplay();
});
}
}
6. Group Related Settingsβ
return new StrategyPanel[] {
createVisualSettingsPanel(), // Colors, widths, styles
createParameterPanel(), // Algorithm parameters
createStatisticsPanel() // Real-time statistics
};
Document Metadataβ
Sourcesβ
Primary Documentation:
CustomSettingsPanelProvider.md(Javadoc)CustomSettingsPanelProvider__SimplifiedApiWrapper_7_7_0_API_.pdf(Official PDF)
Related Classes:
StrategyPanel.md- Panel containerSimpleStrategyPanel.md- Grid layout helperColorsConfigItem.md- Color picker componentGuiUtils.md- UI utility methodsParameter.md- Alternative annotation-based approachStrategySettingsVersion.md- Settings persistence annotationApi.md- Settings API methods
Example Code Analyzed:
SettingsAndUiDemo.java- Complete reference implementationAtrTrailingStopSettings.java- Complex multi-panel with statisticsVolumeTracker.java- Interval and type selectionMboVisualizerNoHistory.java- Real-time display panelStopLossRandomizerSettings.java- Dynamic row addition
Confidence Assessmentβ
| Aspect | Confidence | Reason |
|---|---|---|
| Overall | HIGH | Well-documented with multiple examples |
| Method signature | HIGH | Directly documented in Javadoc |
| Return type | HIGH | Explicitly StrategyPanel[] |
| Panel ordering | HIGH | First panel is topmost (confirmed in examples) |
| Disabled state method | HIGH | Explicitly documented with obfuscator warning |
| Threading model | HIGH | EDT requirement verified in examples |
| ColorsConfigItem callback timing | HIGH | Explicitly documented in SettingsAndUiDemo |
| Settings persistence | HIGH | Multiple examples demonstrate pattern |
Well-Documented Areasβ
- Γ’Εβ¦ Interface purpose and method signature
- Γ’Εβ¦ Disabled state static method
- Γ’Εβ¦ Relationship with @Parameter annotation
- Γ’Εβ¦ StrategyPanel class usage
- Γ’Εβ¦ Settings persistence with @StrategySettingsVersion
- Γ’Εβ¦ ColorsConfigItem callback timing issue
Inferred/Enhanced Areasβ
- Γ°ΕΈβΒ Threading requirements (inferred from Swing best practices)
- Γ°ΕΈβΒ Panel refresh patterns (derived from examples)
- Γ°ΕΈβΒ Throttling strategies (derived from MboVisualizerNoHistory)
- Γ°ΕΈβΒ Best practices (synthesized from multiple examples)
Gaps / Unknown Areasβ
- Γ’Ββ Maximum panel count - No documented limit
- Γ’Ββ Panel size constraints - No explicit sizing documentation
- Γ’Ββ Performance impact - No guidance on complex UI performance
- Γ’Ββ Localization details - setLocalizedTitle usage unclear
- Γ’Ββ requestReload() behavior - Exact refresh mechanism undocumented
Document Version: 1.0
Generated: 2025-01-24
API Interface: velox.api.layer1.simplified.CustomSettingsPanelProvider
Confidence Level: HIGH
Completeness: ~90% (missing only edge case documentation and performance guidance)