mirror of https://github.com/rusefi/rusefi.git
185 lines
6.0 KiB
Java
185 lines
6.0 KiB
Java
package com.rusefi.ui.widgets;
|
|
|
|
import com.rusefi.core.Sensor;
|
|
import com.rusefi.io.CommandQueue;
|
|
import com.rusefi.io.IMethodInvocation;
|
|
import com.rusefi.io.InvocationConfirmationListener;
|
|
import com.rusefi.io.LinkManager;
|
|
import com.rusefi.ui.UIContext;
|
|
import com.rusefi.core.preferences.storage.Node;
|
|
import com.rusefi.ui.util.UiUtils;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.WindowAdapter;
|
|
import java.awt.event.WindowEvent;
|
|
import java.text.DecimalFormat;
|
|
import java.text.Format;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Hashtable;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
/**
|
|
* Andrey Belomutskiy, (c) 2013-2020
|
|
* 11/2/14
|
|
*/
|
|
public class DetachedSensor {
|
|
private static final String NAME = "name";
|
|
private static final String WIDTH = "width";
|
|
|
|
private static final Collection<Sensor> MOCKABLE = Arrays.asList(
|
|
Sensor.CLT,
|
|
Sensor.Lambda,
|
|
Sensor.IAT,
|
|
Sensor.MAF,
|
|
Sensor.MAP,
|
|
Sensor.TPS);
|
|
|
|
private final static Hashtable<Integer, JComponent> SLIDER_LABELS = new Hashtable<>();
|
|
public static final String XPOS = "xpos";
|
|
public static final String YPOS = "ypos";
|
|
|
|
static {
|
|
Format f = new DecimalFormat("0.0");
|
|
for (int i = 0; i <= 50; i += 5) {
|
|
JLabel label = new JLabel(f.format(i * 0.1));
|
|
label.setFont(label.getFont().deriveFont(Font.PLAIN));
|
|
SLIDER_LABELS.put(i, label);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We need to trick the JSlider into displaying float values
|
|
*/
|
|
private static final int _5_VOLTS_WITH_DECIMAL = 50;
|
|
|
|
private final JPanel content = new JPanel(new BorderLayout());
|
|
private final JFrame frame;
|
|
private final JPanel mockControlPanel = new JPanel(new BorderLayout());
|
|
private Sensor sensor;
|
|
@org.jetbrains.annotations.NotNull
|
|
private final UIContext uiContext;
|
|
private int width;
|
|
|
|
public DetachedSensor(UIContext uiContext, Sensor sensor, int width) {
|
|
this.uiContext = uiContext;
|
|
this.width = width;
|
|
frame = new JFrame();
|
|
frame.setAlwaysOnTop(true);
|
|
onChange(sensor);
|
|
|
|
uiContext.DetachedRepositoryINSTANCE.add(this);
|
|
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
|
frame.addWindowListener(new WindowAdapter() {
|
|
@Override
|
|
public void windowClosing(WindowEvent e) {
|
|
uiContext.DetachedRepositoryINSTANCE.remove(DetachedSensor.this);
|
|
frame.dispose();
|
|
}
|
|
});
|
|
create();
|
|
}
|
|
|
|
void create() {
|
|
SensorGauge.GaugeChangeListener listener = this::onChange;
|
|
content.add(SensorGauge.createGauge(uiContext, sensor, listener, null), BorderLayout.CENTER);
|
|
content.add(mockControlPanel, BorderLayout.SOUTH);
|
|
|
|
frame.add(content);
|
|
}
|
|
|
|
public void show(MouseEvent e) {
|
|
frame.setVisible(true);
|
|
frame.setLocation(e.getXOnScreen(), e.getYOnScreen());
|
|
}
|
|
|
|
public void onChange(Sensor sensor) {
|
|
this.sensor = sensor;
|
|
frame.setTitle(sensor.getName());
|
|
showMockControl();
|
|
}
|
|
|
|
public void showMockControl() {
|
|
mockControlPanel.removeAll();
|
|
boolean isMockable = isMockable();
|
|
if (isMockable) {
|
|
Component mockComponent = createMockVoltageSlider(uiContext.getCommandQueue(), sensor);
|
|
mockControlPanel.add(mockComponent);
|
|
}
|
|
UiUtils.trueLayout(content);
|
|
int size = width;
|
|
int h = isMockable ? (int) (size * 1.5) : size;
|
|
frame.setSize(size, h);
|
|
}
|
|
|
|
private boolean isMockable() {
|
|
return MOCKABLE.contains(sensor) && LinkManager.isSimulationMode;
|
|
}
|
|
|
|
public static Component createMockVoltageSlider(CommandQueue commandQueue, final Sensor sensor) {
|
|
final JSlider slider = new JSlider(0, _5_VOLTS_WITH_DECIMAL);
|
|
slider.setLabelTable(SLIDER_LABELS);
|
|
slider.setPaintLabels(true);
|
|
slider.setPaintTicks(true);
|
|
slider.setMajorTickSpacing(10);
|
|
slider.setMinorTickSpacing(5);
|
|
|
|
AtomicReference<Double> pendingValue = new AtomicReference<>();
|
|
|
|
IMethodInvocation commandSender = new IMethodInvocation() {
|
|
@Override
|
|
public String getCommand() {
|
|
return "set mock_" + sensor.name().toLowerCase() + "_voltage " + pendingValue.get();
|
|
}
|
|
|
|
@Override
|
|
public int getTimeout() {
|
|
return CommandQueue.DEFAULT_TIMEOUT;
|
|
}
|
|
|
|
@Override
|
|
public InvocationConfirmationListener getListener() {
|
|
return InvocationConfirmationListener.VOID;
|
|
}
|
|
|
|
@Override
|
|
public boolean isFireEvent() {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
slider.addChangeListener(e -> {
|
|
double value = slider.getValue() / 10.0;
|
|
pendingValue.set(value);
|
|
|
|
/*
|
|
* User might be changing slider faster than commands are being send
|
|
* We only add commandSender into the queue only if not already pending in order to only send one command with latest requested value and not a sequence of commands.
|
|
*/
|
|
commandQueue.addIfNotPresent(commandSender);
|
|
});
|
|
|
|
return slider;
|
|
}
|
|
|
|
public void saveConfig(Node child) {
|
|
child.setProperty(NAME, sensor.name());
|
|
child.setProperty(WIDTH, frame.getWidth());
|
|
child.setProperty(XPOS, frame.getLocation().x);
|
|
child.setProperty(YPOS, frame.getLocation().y);
|
|
}
|
|
|
|
public static void create(UIContext uiContext, Node child) {
|
|
Sensor sensor = Sensor.lookup(child.getProperty(NAME, Sensor.RPMValue.name()), Sensor.RPMValue);
|
|
int width = child.getIntProperty(WIDTH, 256);
|
|
int xpos = child.getIntProperty(XPOS, 0);
|
|
int ypos = child.getIntProperty(YPOS, 0);
|
|
DetachedSensor ds = new DetachedSensor(uiContext, sensor, width);
|
|
ds.frame.setLocation(xpos, ypos);
|
|
ds.frame.setVisible(true);
|
|
}
|
|
}
|