us.ihmc.simulationconstructionset.gui.yoVariableSearch.YoVariablePanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of simulation-construction-set
Show all versions of simulation-construction-set
Simulation Construction Set
package us.ihmc.simulationconstructionset.gui.yoVariableSearch;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import us.ihmc.graphicsDescription.graphInterfaces.SelectedVariableHolder;
import us.ihmc.simulationconstructionset.SimulationConstructionSet;
import us.ihmc.simulationconstructionset.gui.DoubleClickListener;
import us.ihmc.simulationconstructionset.gui.EventDispatchThreadHelper;
import us.ihmc.simulationconstructionset.gui.HorizontalSpinnerUI;
import us.ihmc.simulationconstructionset.gui.YoGraph;
import us.ihmc.yoVariables.listener.YoVariableChangedListener;
import us.ihmc.yoVariables.registry.YoVariableList;
import us.ihmc.yoVariables.variable.YoVariable;
import us.ihmc.yoVariables.variable.YoVariableType;
public abstract class YoVariablePanel extends JPanel implements KeyListener, MouseListener, FocusListener, ComponentListener
{
private static final long serialVersionUID = -5860355582880221731L;
private static ArrayList variableChangedListeners = new ArrayList<>();
protected static final int SPINNER_HEIGHT = 16;
protected static final int MINIMUM_TOTAL_WIDTH = 220;
private static final int SPINNER_WIDTH = 84;
protected final SelectedVariableHolder selectedVariableHolder;
protected final YoVariablePanelJPopupMenu varPanelJPopupMenu;
protected int selectedVariableIndex = -1;
private boolean hasFocus = false;
private final ArrayList yoVariableSpinners = new ArrayList<>(0);
private final NumberFormat numberFormat;
private DoubleClickListener doubleClickListener;
private static final Color EMPTY_COLOR = Color.red;
private static final Color REGULAR_COLOR = Color.black;
private static final Color SELECTED_COLOR = Color.red;
private static boolean showNamespace = false;
private final YoVariableSearchPanel searchPanel;
public static void attachVariableChangedListener(YoVariableChangedListener listener)
{
variableChangedListeners.add(listener);
}
public static boolean areNamespacesShown()
{
return showNamespace;
}
public static void removeVariableChangedListener(YoVariableChangedListener listener)
{
variableChangedListeners.remove(listener);
}
public static void addNamespaceToVarNames()
{
showNamespace = !showNamespace;
}
public YoVariablePanel(String name, SelectedVariableHolder holder, YoVariablePanelJPopupMenu varPanelJPopupMenu, YoVariableSearchPanel searchPanel)
{
selectedVariableHolder = holder;
setName(name);
this.varPanelJPopupMenu = varPanelJPopupMenu;
varPanelJPopupMenu.addFocusListener(this);
numberFormat = NumberFormat.getInstance();
numberFormat.setMaximumFractionDigits(4);
numberFormat.setMinimumFractionDigits(1);
numberFormat.setGroupingUsed(false);
this.searchPanel = searchPanel;
init();
}
public YoVariablePanel(YoVariableList list, SelectedVariableHolder holder, YoVariablePanelJPopupMenu varPanelJPopupMenu)
{
this(list.getName(), holder, varPanelJPopupMenu, null);
}
public YoVariablePanel(String name, SelectedVariableHolder holder, YoVariablePanelJPopupMenu varPanelJPopupMenu)
{
this(name, holder, varPanelJPopupMenu, null);
}
protected abstract int getNumberOfYoVariables();
protected abstract YoVariable getYoVariable(int index);
protected abstract List getAllYoVariablesCopy();
public abstract YoVariable getYoVariable(String string);
private void init()
{
setLayout(null);
addMouseListener(this);
addMouseMotionListener(new ToolTipMouseMotionListener());
addKeyListener(this);
setRequestFocusEnabled(true);
setTransferHandler(new YoVariablePanelTransferHandler());
setMinimumSize(new Dimension(MINIMUM_TOTAL_WIDTH, SPINNER_HEIGHT));
addComponentListener(this);
selectedVariableHolder.addChangeListener(new UpdateUIChangeListener());
addFocusListener(this);
}
protected void clearAndSetUpTextFields()
{
EventDispatchThreadHelper.invokeAndWait(new Runnable()
{
@Override
public void run()
{
yoVariableSpinners.clear();
for (int i = 0; i < getNumberOfYoVariables(); i++)
{
addTextFieldForVariable(getYoVariable(i));
}
}
});
}
protected void addTextFieldForVariable(final YoVariable yoVariable)
{
JSpinner spinner = createSpinnerForVariable(yoVariable);
synchronized (yoVariableSpinners)
{
yoVariableSpinners.add(spinner);
}
add(spinner);
setPanelSize(getNumberOfYoVariables());
}
private JSpinner createSpinnerForVariable(final YoVariable yoVariable)
{
SpinnerNumberModel spinnerNumberModel = new SpinnerNumberModel();
spinnerNumberModel.setStepSize(yoVariable.getType() == YoVariableType.DOUBLE ? 0.1 : 1.0);
spinnerNumberModel.setValue(yoVariable.getValueAsDouble());
final JSpinner spinner = new JSpinner(spinnerNumberModel);
spinner.setName(yoVariable.getFullNameString());
String tooltip = "You can scroll to modify the current value
Press shift to be more precise OR Control to change by bigger increments";
spinner.setToolTipText(tooltip);
spinner.addMouseWheelListener(new MouseWheelListener()
{
private static final double SCROLL_FACTOR = 1;
private static final double SHIFT_FACTOR = 0.1;
private static final double CTRL_FACTOR = 10;
@Override
public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent)
{
double modifierFactor = (mouseWheelEvent.isShiftDown()) ? SHIFT_FACTOR : (mouseWheelEvent.isControlDown()) ? CTRL_FACTOR : 1;
double currentValue = Double.parseDouble(spinner.getValue().toString());
double delta = spinnerNumberModel.getStepSize().doubleValue() * mouseWheelEvent.getWheelRotation() * SCROLL_FACTOR * modifierFactor;
spinner.setValue(currentValue + delta);
}
});
spinner.setUI(new HorizontalSpinnerUI());
spinner.setBorder(null);
spinner.setBackground(getBackground());
int width = SPINNER_WIDTH;
int height = SPINNER_HEIGHT;
int x = getViewableWidth() - SPINNER_WIDTH;
int y = yoVariableSpinners.size() * height;
spinner.setBounds(x, y, width, height);
spinner.addChangeListener(new SetYoVariableBasedOnTextFieldActionListener(spinner, yoVariable));
spinner.addFocusListener(this);
return spinner;
}
public void refreshPanelWidth()
{
for (int i = 0; i < yoVariableSpinners.size(); i++)
{
yoVariableSpinners.get(i).setLocation(getViewableWidth() - SPINNER_WIDTH, i * SPINNER_HEIGHT);
}
}
public boolean isEmpty()
{
return (getNumberOfYoVariables() == 0);
}
public void setPanelSize(int nVars)
{
setPreferredSize(new Dimension(getViewableWidth() < MINIMUM_TOTAL_WIDTH ? MINIMUM_TOTAL_WIDTH : getViewableWidth(), SPINNER_HEIGHT * nVars));
}
public int getViewableWidth()
{
return (int) getVisibleRect().getWidth();
}
@Override
public void paintComponent(Graphics graphics)
{
super.paintComponent(graphics);
int xStringStart = 2;
int yStringStart = 10;
if (isEmpty())
{
graphics.setColor(EMPTY_COLOR);
String text = new String("Empty");
graphics.drawString(text, xStringStart, yStringStart);
}
else
{
List allVariables = getAllYoVariablesCopy();
graphics.setColor(REGULAR_COLOR);
YoVariable selectedVariable = selectedVariableHolder.getSelectedVariable();
Rectangle rectangle = getVisibleRect();
int minIndexOfVisibleVariables = getMinIndexOfVisibleVariables(rectangle);
int maxIndexOfVisibleVariables = getMaxIndexOfVisibleVariables(rectangle);
int longestLengthAllowed = getWidth() - (SPINNER_WIDTH + yStringStart);
for (int i = minIndexOfVisibleVariables; i < maxIndexOfVisibleVariables; i++)
{
YoVariable v = allVariables.get(i);
JTextField jTextField = null;
synchronized (yoVariableSpinners)
{
if ((i >= 0) && (i < yoVariableSpinners.size()))
{
jTextField = ((JSpinner.DefaultEditor) yoVariableSpinners.get(i).getEditor()).getTextField();
}
}
if (jTextField != null)
{
if (selectedVariable == v)
{
graphics.setColor(SELECTED_COLOR);
}
else
{
graphics.setColor(REGULAR_COLOR);
}
String formattedName = formatName(graphics, longestLengthAllowed, v);
graphics.drawString(formattedName, xStringStart, i * SPINNER_HEIGHT + yStringStart);
if (!jTextField.hasFocus())
{
double value = v.getValueAsDouble();
String text = formatDouble(value);
if (!text.equals(jTextField.getText())) // otherwise there will be an infinite update loop
{
jTextField.setText(text);
jTextField.setCaretPosition(0);
}
}
}
}
}
}
private String formatName(Graphics graphics, int longestLengthAllowed, YoVariable v)
{
// fairly inefficient, could do a binary search instead
String formattedName;
FontMetrics fontMetrics = graphics.getFontMetrics();
String name = "";
if (showNamespace)
{
name = v.getNamespace().getShortName() + "." + v.getName();
}
else
name = v.getName();
if (fontMetrics.stringWidth(name) < longestLengthAllowed)
{
formattedName = name;
}
else
{
String ellipsis = "...";
int lengthOfEllipsis = fontMetrics.stringWidth(ellipsis);
int firstHalfLength = 1;
int lengthAvailableForName = longestLengthAllowed - lengthOfEllipsis;
int lengthAvailableForNamePart = lengthAvailableForName / 2;
while ((firstHalfLength < name.length()) && (fontMetrics.stringWidth(name.substring(0, firstHalfLength)) < lengthAvailableForNamePart))
{
firstHalfLength++;
}
firstHalfLength--;
String firstHalf = name.substring(0, firstHalfLength);
int lastHalfLength = name.length();
while ((lastHalfLength >= 0) && (fontMetrics.stringWidth(name.substring(lastHalfLength, name.length())) < lengthAvailableForNamePart))
{
lastHalfLength--;
}
lastHalfLength++;
String lastHalf = name.substring(lastHalfLength, name.length());
formattedName = firstHalf + ellipsis + lastHalf;
}
return formattedName;
}
private String formatDouble(double value)
{
String text;
if (Double.isNaN(value) || Double.isInfinite(value))
{
text = Double.toString(value);
}
else
{
text = numberFormat.format(value);
}
return text;
}
public SelectedVariableHolder getVariableHolder()
{
return selectedVariableHolder;
}
protected List getYoVariableSpinners()
{
return yoVariableSpinners;
}
@Override
public void mousePressed(MouseEvent event)
{
if (!isFocusOwner())
{
if (!this.requestFocusInWindow())
{
System.err.println("Warning: VarPanel failed to gain focus on mousePressed.");
}
}
varPanelJPopupMenu.setVisible(false);
if (SwingUtilities.isLeftMouseButton(event))
{
int indexOfSelectedVariable = getIndexOfClickedVariable(event.getY());
if ((indexOfSelectedVariable >= 0) && (indexOfSelectedVariable < yoVariableSpinners.size()))
{
yoVariableSpinners.get(indexOfSelectedVariable).requestFocusInWindow();
}
YoVariable selectedVariable = getClickedYoVariable(event.getY());
if (selectedVariable != null)
{
selectedVariableHolder.setSelectedVariable(selectedVariable);
if (!SimulationConstructionSet.DISABLE_DnD)
{
JComponent c = (JComponent) event.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, event, TransferHandler.COPY);
YoGraph.setSourceOfDrag(this);
}
repaint();
}
}
else if (SwingUtilities.isRightMouseButton(event))
{
YoVariable selectedVariable = getClickedYoVariable(event.getY());
if (selectedVariable != null)
{
selectedVariableHolder.setSelectedVariable(selectedVariable);
varPanelJPopupMenu.setLocation(event.getXOnScreen(), event.getYOnScreen());
varPanelJPopupMenu.setVisible(true);
}
}
}
private YoVariable getClickedYoVariable(int mouseY)
{
selectedVariableIndex = getIndexOfClickedVariable(mouseY);
if (selectedVariableIndex >= 0)
{
return getYoVariable(selectedVariableIndex);
}
return null;
}
private int getIndexOfClickedVariable(int mouseY)
{
int indexOfClickedVariable = mouseY / SPINNER_HEIGHT;
if ((indexOfClickedVariable >= 0) && (indexOfClickedVariable < getNumberOfYoVariables()))
{
return indexOfClickedVariable;
}
return -1;
}
@Override
public void mouseReleased(MouseEvent evt)
{
}
@Override
public void mouseEntered(MouseEvent evt)
{
}
@Override
public void mouseExited(MouseEvent evt)
{
}
@Override
public void mouseClicked(MouseEvent evt)
{
int y = evt.getY();
if (y < 0)
{
return;
}
selectedVariableIndex = y / SPINNER_HEIGHT;
if (isEmpty())
{
selectedVariableIndex = -1;
}
else if (selectedVariableIndex > getNumberOfYoVariables())
{
selectedVariableIndex = -1;
}
if ((selectedVariableIndex >= 0) && (selectedVariableIndex < getNumberOfYoVariables()))
{
YoVariable selectedVariable = getYoVariable(selectedVariableIndex);
setSelected(selectedVariable);
}
// See if double click:
if (evt.getClickCount() == 2)
{
YoVariable selectedVariable = selectedVariableHolder.getSelectedVariable();
if ((selectedVariable != null) && (doubleClickListener != null))
{
doubleClickListener.doubleClicked(selectedVariable);
}
}
this.requestFocus();
this.repaint();
}
public void setDoubleClickListener(DoubleClickListener listener)
{
doubleClickListener = listener;
}
@Override
public void keyTyped(KeyEvent evt)
{
}
@Override
public void keyReleased(KeyEvent evt)
{
}
@Override
public void keyPressed(KeyEvent evt)
{
int code = evt.getKeyCode();
switch (code)
{
case KeyEvent.VK_UP:
selectedVariableIndex = Math.max(0, selectedVariableIndex - 1);
break;
case KeyEvent.VK_DOWN:
selectedVariableIndex = Math.min(getNumberOfYoVariables() - 1, selectedVariableIndex + 1);
break;
}
if ((selectedVariableIndex < 0) || (selectedVariableIndex > getNumberOfYoVariables()))
{
setSelected(null);
}
else
{
setSelected(getYoVariable(selectedVariableIndex));
}
this.repaint();
}
private void setSelected(YoVariable var)
{
selectedVariableHolder.setSelectedVariable(var);
}
@Override
public void focusGained(FocusEvent e)
{
if (e.getSource().equals(this))
{
if (hasFocus)
{
varPanelJPopupMenu.setVisible(false);
}
}
else if (e.getSource() instanceof JSpinner)
{
JSpinner spinner = (JSpinner) e.getSource();
JTextField textField = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
Rectangle rectangle = getVisibleRect();
int minIndexOfVisibleVariables = getMinIndexOfVisibleVariables(rectangle);
int maxIndexOfVisibleVariables = getMaxIndexOfVisibleVariables(rectangle);
int indexOfFocusedTextField = yoVariableSpinners.indexOf(spinner);
int focusedTextFieldYPositionOnPanel = indexOfFocusedTextField * SPINNER_HEIGHT;
if ((indexOfFocusedTextField <= minIndexOfVisibleVariables) || (indexOfFocusedTextField >= maxIndexOfVisibleVariables))
{
scrollRectToVisible(new Rectangle(0, focusedTextFieldYPositionOnPanel, spinner.getBounds().width, SPINNER_HEIGHT));
}
YoVariable yoVariable = getYoVariable(indexOfFocusedTextField);
if (yoVariable != null)
{
selectedVariableHolder.setSelectedVariable(yoVariable);
this.repaint();
}
textField.setCaretPosition(textField.getText().length());
textField.moveCaretPosition(0);
}
hasFocus = true;
}
private int getMinIndexOfVisibleVariables(Rectangle rectangle)
{
int size = getNumberOfYoVariables();
int minIndexOfVisibleVariables = rectangle.y / SPINNER_HEIGHT;
minIndexOfVisibleVariables = Math.max(minIndexOfVisibleVariables, 0);
minIndexOfVisibleVariables = Math.min(minIndexOfVisibleVariables, size);
return minIndexOfVisibleVariables;
}
private int getMaxIndexOfVisibleVariables(Rectangle rectangle)
{
int size = getNumberOfYoVariables();
int maxIndexOfVisibleVariables = (rectangle.y + rectangle.height) / SPINNER_HEIGHT;
maxIndexOfVisibleVariables = Math.max(maxIndexOfVisibleVariables, 0);
maxIndexOfVisibleVariables = Math.min(maxIndexOfVisibleVariables, size);
return maxIndexOfVisibleVariables;
}
@Override
public void focusLost(FocusEvent e)
{
varPanelJPopupMenu.setVisible(false);
hasFocus = false;
}
@Override
public void componentHidden(ComponentEvent e)
{
}
@Override
public void componentMoved(ComponentEvent e)
{
}
@Override
public void componentResized(ComponentEvent e)
{
synchronized (yoVariableSpinners)
{
if (e.getSource().equals(this))
{
int width = this.getSize().width;
for (JComponent textField : yoVariableSpinners)
{
Rectangle currentBounds = textField.getBounds();
textField.setBounds(width - SPINNER_WIDTH, currentBounds.y, currentBounds.width, currentBounds.height);
}
}
}
}
@Override
public void componentShown(ComponentEvent e)
{
}
private final class ToolTipMouseMotionListener implements MouseMotionListener
{
@Override
public void mouseMoved(MouseEvent event)
{
YoVariable yoVariable = getClickedYoVariable(event.getY());
if (yoVariable != null)
{
String displayText = yoVariable.getFullNameString();
String descriptionText = yoVariable.getDescription();
if (!(descriptionText.equals(null) || descriptionText.equals("")))
{
displayText += "\nDescription: (" + descriptionText + ")";
}
displayText = displayText.replaceAll("\n", "
");
displayText = "" + displayText + "";
setToolTipText(displayText);
}
}
@Override
public void mouseDragged(MouseEvent e)
{
}
}
private final class UpdateUIChangeListener implements ChangeListener
{
@Override
public void stateChanged(ChangeEvent e)
{
updateUI();
}
}
private final class SetYoVariableBasedOnTextFieldActionListener implements ChangeListener
{
private final JSpinner spinner;
private final YoVariable variable;
private SetYoVariableBasedOnTextFieldActionListener(JSpinner spinner, YoVariable variable)
{
this.spinner = spinner;
this.variable = variable;
}
@Override
public void stateChanged(ChangeEvent e)
{
try
{
variable.setValueFromDouble(Double.parseDouble(spinner.getValue().toString()));
}
catch (RuntimeException exceptionToIgnore)
{
}
spinner.setValue(variable.getValueAsDouble());
for (int i = 0; i < variableChangedListeners.size(); i++)
{
YoVariableChangedListener listener = variableChangedListeners.get(i);
listener.changed(variable);
}
spinner.requestFocusInWindow();
}
}
}