/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.ui;
import org.esa.snap.core.dataop.barithm.BandArithmetic;
import org.esa.snap.core.jexp.Function;
import org.esa.snap.core.jexp.Namespace;
import org.esa.snap.core.jexp.ParseException;
import org.esa.snap.core.jexp.Parser;
import org.esa.snap.core.jexp.Term;
import org.esa.snap.core.jexp.impl.Functions;
import org.esa.snap.core.jexp.impl.NamespaceImpl;
import org.esa.snap.core.util.PropertyMap;
import org.esa.snap.ui.tool.ToolButtonFactory;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;
/**
* The expression pane is a UI component which is used to edit mathematical expressions. There are four methods which
* can be used to customize the UI of the expression pane: {@code {@link #setLeftAccessory}}, {@code {@link
* #setRightAccessory}}, {@code {@link #setTopAccessory}} and {@code {@link #setBottomAccessory}}.
*/
public class ExpressionPane extends JPanel {
public static final String HELP_ID = "expressionEditor";
/**
* The prefix used to store the code history in the preferences.
*/
public static final String CODE_HISTORY_PREFERENCES_PREFIX = "expression.history.";
private static final int CODE_HISTORY_MAX = 100;
/**
* The string used to represent an expression placeholder for text insertion.
*/
public static final String PLACEHOLDER = "@";
private static final int PLACEHOLDER_LEN = PLACEHOLDER.length();
private static final String[] CONSTANT_LITERALS = new String[]{
"PI",
"E",
"NaN",
"true",
"false",
"X",
"Y",
"LAT",
"LON",
"TIME",
"0.5",
"0.0",
"1.0",
"2.0",
"0",
"1",
"2",
"273.15",
};
private static final String[] OPERATOR_PATTERNS = new String[]{
"@ ? @ : @",
"if @ then @ else @",
"@ || @",
"@ or @",
"@ && @",
"@ and @",
"@ < @",
"@ <= @",
"@ > @",
"@ >= @",
"@ == @",
"@ <= @",
"@ | @",
"@ ^ @",
"@ & @",
"@ + @",
"@ - @",
"@ * @",
"@ / @",
"@ % @",
"+@",
"-@",
"~@",
"!@",
"not @"
};
private static final String[] FUNCTION_CALL_PATTERNS;
private static Font exprTextAreaFont = new Font("Courier", Font.PLAIN, 12);
private static Font insertCompFont = new Font("Courier", Font.PLAIN, 11);
private static Color insertCompColor = new Color(0, 0, 128);
private static Color okMsgColor = new Color(0, 128, 0);
private static Color warnMsgColor = new Color(128, 0, 0);
private Parser parser;
private final Stack<String> undoBuffer;
private boolean booleanExpressionPreferred;
private JTextArea codeArea;
private JLabel messageLabel;
private ExpressionPane.ActionPane actionPane;
private String lastErrorMessage;
private PropertyMap preferences;
private List<String> history;
private int historyIndex;
private boolean emptyExpressionAllowed;
static {
List<Function> functions = Functions.getAll();
FUNCTION_CALL_PATTERNS = new String[functions.size()];
for (int i = 0; i < FUNCTION_CALL_PATTERNS.length; i++) {
FUNCTION_CALL_PATTERNS[i] = getFunctionCallPattern(functions.get(i));
}
}
/**
* Constructs a new expression pane.
*
* @param requiresBoolExpr if {@code true} the expressions are checked to return a boolean value.
* @param parser the parser used to check expression syntax
* @param preferences a property map which stores expression pane related properties such as the code history
*/
public ExpressionPane(boolean requiresBoolExpr, Parser parser, PropertyMap preferences) {
super(new BorderLayout(4, 4));
undoBuffer = new Stack<>();
this.parser = parser;
booleanExpressionPreferred = requiresBoolExpr;
history = new LinkedList<>();
historyIndex = -1;
emptyExpressionAllowed = true;
setPreferences(preferences);
createUI();
}
public int showModalDialog(Window parent, String title) {
ModalDialog dialog = new ExpressionPaneDialog(parent, title);
return dialog.show();
}
public PropertyMap getPreferences() {
return preferences;
}
public void setPreferences(PropertyMap preferences) {
this.preferences = preferences;
if (this.preferences != null) {
loadCodeHistory();
}
}
public void setEmptyExpressionAllowed(boolean allow) {
this.emptyExpressionAllowed = allow;
}
public boolean isEmptyExpressionAllowed() {
return emptyExpressionAllowed;
}
public void updateCodeHistory() {
String code = getCode();
if (code != null) {
code = code.trim();
if (!code.equals("")) {
addToCodeHistory(code, true);
storeCodeHistory();
}
}
}
private void addToCodeHistory(String code, boolean head) {
if (code != null) {
code = code.trim();
if (!code.equals("")) {
if (history.contains(code)) {
history.remove(code);
}
if (head) {
history.add(0, code);
} else {
history.add(code);
}
}
}
}
public void loadCodeHistory() {
if (preferences != null) {
history.clear();
for (int index = 0; index < CODE_HISTORY_MAX; index++) {
final String code = preferences.getPropertyString(CODE_HISTORY_PREFERENCES_PREFIX + index);
addToCodeHistory(code, false);
}
historyIndex = -1;
updateUIState();
}
}
public void storeCodeHistory() {
if (history != null && preferences != null) {
final Iterator<String> iterator = history.iterator();
for (int index = 0; index < CODE_HISTORY_MAX && iterator.hasNext(); index++) {
String code = iterator.next();
preferences.setPropertyString(CODE_HISTORY_PREFERENCES_PREFIX + index, code);
}
}
}
protected void dispose() {
undoBuffer.clear();
parser = null;
codeArea = null;
messageLabel = null;
actionPane = null;
}
public void setLeftAccessory(Component component) {
add(component, BorderLayout.WEST);
}
public void setRightAccessory(Component component) {
add(component, BorderLayout.EAST);
}
public void setTopAccessory(Component component) {
add(component, BorderLayout.NORTH);
}
public void setBottomAccessory(Component component) {
add(component, BorderLayout.SOUTH);
}
public JTextArea getCodeArea() {
return codeArea;
}
public boolean isBooleanExpressionPreferred() {
return booleanExpressionPreferred;
}
public void setBooleanExpressionPreferred(boolean booleanExpressionPreferred) {
this.booleanExpressionPreferred = booleanExpressionPreferred;
}
public Parser getParser() {
return parser;
}
public void setParser(Parser parser) {
Parser oldValue = this.parser;
if (oldValue == parser) {
return;
}
this.parser = parser;
firePropertyChange("parser", oldValue, parser);
}
public String getCode() {
return codeArea.getText();
}
public void setCode(String newCode) {
setCode(newCode, false, -1);
}
public void setCode(String newCode, boolean recordUndo, int caretPos) {
String oldCode = codeArea.getText();
if (recordUndo) {
pushCodeOnUndoStack(oldCode);
}
codeArea.setText(newCode == null ? "" : newCode);
checkCode(newCode);
updateUIState();
if (caretPos >= 0) {
codeArea.setCaretPosition(caretPos);
}
codeArea.requestFocus();
firePropertyChange("code", oldCode, newCode);
}
public void clearCode() {
setCode("");
}
public void selectAllCode() {
codeArea.selectAll();
codeArea.requestFocus();
}
public void undoLastEdit() {
if (!undoBuffer.isEmpty()) {
String code = undoBuffer.pop();
setCode(code);
updateUIState();
codeArea.requestFocus();
}
}
public void insertCodePattern(String pattern) {
String oldCode = getCode();
int newCaretPos;
StringBuffer sb = new StringBuffer(oldCode.length() + 2 * pattern.length());
int selPos1 = codeArea.getSelectionStart();
int selPos2 = codeArea.getSelectionEnd();
if (selPos1 >= 0 && selPos2 >= 0 && selPos1 > selPos2) {
int temp = selPos1;
selPos1 = selPos2;
selPos2 = temp;
}
int phPatPos = pattern.indexOf(PLACEHOLDER);
// If code was selected,
if (selPos2 > selPos1) {
String selCode = oldCode.substring(selPos1, selPos2);
// ...look if there is a placeholder in the pattern
append(sb, oldCode.substring(0, selPos1));
if (phPatPos >= 0) {
// replace placeholder in pattern with selected text
append(sb, pattern.substring(0, phPatPos));
append(sb, selCode.trim());
append(sb, pattern.substring(phPatPos + PLACEHOLDER_LEN));
} else {
// replace selected text with pattern
append(sb, pattern);
}
newCaretPos = sb.length();
append(sb, oldCode.substring(selPos2));
} else {
// If no code was selected,
// ...look if there is a placeholder in the code
int phPos = oldCode.indexOf(PLACEHOLDER);
if (phPos >= 0 && phPatPos == -1) {
// replace placeholder in code with pattern
append(sb, oldCode.substring(0, phPos));
append(sb, pattern);
newCaretPos = sb.length();
append(sb, oldCode.substring(phPos + PLACEHOLDER_LEN));
} else {
// ... look for the caret pos
int caretPos = codeArea.getCaretPosition();
// ... and divide code in a left and right part
String lCode = oldCode.substring(0, caretPos).trim();
String rCode = oldCode.substring(caretPos).trim();
// if there is text to the left and the pattern starts with a placeholder
if (lCode.length() > 0 && pattern.startsWith(PLACEHOLDER)) {
// if there is text to the right and the pattern ends with a placeholder
if (rCode.length() > 0 && pattern.endsWith(PLACEHOLDER)) {
// ...replace both placeholder in the pattern
append(sb, lCode);
append(sb, pattern.substring(PLACEHOLDER_LEN, pattern.length() - PLACEHOLDER_LEN));
newCaretPos = sb.length();
append(sb, rCode);
} else {
// ...replace left placeholder in the pattern
append(sb, lCode);
append(sb, pattern.substring(PLACEHOLDER_LEN));
newCaretPos = sb.length();
append(sb, rCode);
}
// if there is text to the right and the pattern ends with a placeholder
} else if (rCode.length() > 0 && pattern.endsWith(PLACEHOLDER)) {
// ...replace right placeholder in the pattern
append(sb, lCode);
append(sb, pattern.substring(0, pattern.length() - PLACEHOLDER_LEN));
newCaretPos = sb.length();
append(sb, rCode);
} else {
// ...insert pattern at caret position
append(sb, lCode);
append(sb, pattern);
newCaretPos = sb.length();
append(sb, rCode);
}
}
}
setCode(sb.toString(), true, newCaretPos);
}
private static void append(StringBuffer sb, String s) {
int n1 = sb.length();
int n2 = s.length();
if (n1 > 0 && n2 > 0) {
char ch1 = sb.charAt(n1 - 1);
char ch2 = s.charAt(0);
if (ch1 != ' ' && ch2 != ' ' && ch1 != ',' && ch1 != '(' && ch2 != ')') {
sb.append(' ');
}
}
sb.append(s);
}
public ActionPane createActionPane() {
return new ActionPane();
}
public JButton createInsertButton(final String pattern) {
JButton button = new JButton(pattern);
button.setFont(insertCompFont);
button.setForeground(insertCompColor);
button.addActionListener(e -> insertCodePattern(pattern));
return button;
}
private JComboBox<String> createInsertComboBox(final String title, final String[] patterns) {
ArrayList<String> itemList = new ArrayList<>();
itemList.add(title);
itemList.addAll(Arrays.asList(patterns));
final JComboBox<String> comboBox = new JComboBox<>(itemList.toArray(new String[itemList.size()]));
comboBox.setFont(insertCompFont);
comboBox.setEditable(false);
comboBox.setForeground(insertCompColor);
comboBox.addActionListener(e -> {
if (comboBox.getSelectedIndex() != 0) {
insertCodePattern((String) comboBox.getSelectedItem());
comboBox.setSelectedIndex(0);
}
});
return comboBox;
}
public JList<String> createPatternList() {
return createPatternList(null);
}
public JList<String> createPatternList(final String[] patterns) {
final JList<String> patternList = new JList<>(patterns);
patternList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
final ListCellRenderer<? super String> cellRenderer = patternList.getCellRenderer();
final Border cellBorder = BorderFactory.createEtchedBorder();
patternList.setCellRenderer((list, value, index, isSelected, cellHasFocus) -> {
final Component component1 = cellRenderer.getListCellRendererComponent(list, value, index, isSelected,
cellHasFocus);
if (component1 instanceof JComponent) {
((JComponent) component1).setBorder(cellBorder);
}
return component1;
});
patternList.setFont(insertCompFont);
patternList.setBackground(getBackground());
patternList.setForeground(insertCompColor);
patternList.addMouseListener(new MouseAdapter() {
/**
* Invoked when the mouse has been clicked on a component.
*/
@Override
public void mouseClicked(MouseEvent e) {
final int index = patternList.locationToIndex(e.getPoint());
if (index >= 0) {
final String value = patternList.getModel().getElementAt(index);
final String pattern = BandArithmetic.createExternalName(value);
insertCodePattern(pattern);
patternList.clearSelection();
}
}
});
return patternList;
}
protected JPanel createPatternListPane(final String labelText, final String[] patterns) {
JList<String> list = createPatternList(patterns);
JScrollPane scrollableList = new JScrollPane(list);
JPanel pane = new JPanel(new BorderLayout());
pane.add(BorderLayout.NORTH, new JLabel(labelText));
pane.add(BorderLayout.CENTER, scrollableList);
return pane;
}
protected void createUI() {
codeArea = new JTextArea(10, 40);
codeArea.setName("codeArea");
codeArea.setLineWrap(true);
codeArea.setWrapStyleWord(true);
codeArea.setFont(exprTextAreaFont);
codeArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
checkCode();
}
public void removeUpdate(DocumentEvent e) {
checkCode();
}
public void changedUpdate(DocumentEvent e) {
checkCode();
}
});
actionPane = createActionPane();
actionPane.setName("actionPane");
messageLabel = new JLabel();
messageLabel.setFont(getFont().deriveFont(10.0F));
messageLabel.setHorizontalAlignment(JLabel.RIGHT);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(actionPane, BorderLayout.WEST);
panel.add(messageLabel, BorderLayout.EAST);
JScrollPane scrollableTextArea = new JScrollPane(codeArea);
JPanel codePane = new JPanel(new BorderLayout());
codePane.add(new JLabel("Expression:"), BorderLayout.NORTH); /*I18N*/
codePane.add(scrollableTextArea, BorderLayout.CENTER);
codePane.add(panel, BorderLayout.SOUTH);
add(codePane, BorderLayout.CENTER);
setCode("");
}
protected JPanel createPatternInsertionPane() {
final GridBagLayout gbl = new GridBagLayout();
JPanel patternPane = new JPanel(gbl);
GridBagConstraints gbc = new GridBagConstraints();
gbc.ipadx = 1;
gbc.ipady = 1;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
gbc.gridy = 0;
if (booleanExpressionPreferred) {
final JButton andButton = createInsertButton("@ and @");
final JButton orButton = createInsertButton("@ or @");
final JButton notButton = createInsertButton("not @");
andButton.setName("andButton");
orButton.setName("orButton");
notButton.setName("notButton");
add(patternPane, andButton, gbc);
gbc.gridy++;
add(patternPane, orButton, gbc);
gbc.gridy++;
add(patternPane, notButton, gbc);
gbc.gridy++;
} else {
final JButton plusButton = createInsertButton("@ + @");
final JButton minusButton = createInsertButton("@ - @");
final JButton mulButton = createInsertButton("@ * @");
final JButton divButton = createInsertButton("@ / @");
plusButton.setName("plusButton");
minusButton.setName("minusButton");
mulButton.setName("mulButton");
divButton.setName("divButton");
add(patternPane, plusButton, gbc);
gbc.gridy++;
add(patternPane, minusButton, gbc);
gbc.gridy++;
add(patternPane, mulButton, gbc);
gbc.gridy++;
add(patternPane, divButton, gbc);
gbc.gridy++;
}
final String[] functionNames = getFunctionTemplates();
final JButton parenButton = createInsertButton("(@)");
parenButton.setName("parenButton");
final JComboBox<String> functBox = createInsertComboBox("Functions...", functionNames);
final JComboBox<String> operBox = createInsertComboBox("Operators...", OPERATOR_PATTERNS);
final JComboBox<String> constBox = createInsertComboBox("Constants...", CONSTANT_LITERALS);
functBox.setName("functBox");
operBox.setName("operBox");
constBox.setName("constBox");
add(patternPane, parenButton, gbc);
gbc.gridy++;
add(patternPane, constBox, gbc);
gbc.gridy++;
add(patternPane, operBox, gbc);
gbc.gridy++;
add(patternPane, functBox, gbc);
gbc.gridy++;
return patternPane;
}
private String[] getFunctionTemplates() {
final Namespace defaultNamespace = parser.getDefaultNamespace();
// collect names
String[] functionNames;
if (defaultNamespace instanceof NamespaceImpl) {
final NamespaceImpl namespace = (NamespaceImpl) defaultNamespace;
final Function[] functions = namespace.getAllFunctions();
functionNames = new String[functions.length];
for (int i = 0; i < functions.length; i++) {
functionNames[i] = createFunctionTemplate(functions[i]);
}
} else {
functionNames = FUNCTION_CALL_PATTERNS;
}
// remove double values
Set<String> set = new HashSet<>();
Collections.addAll(set, functionNames);
functionNames = set.toArray(new String[set.size()]);
Arrays.sort(functionNames);
return functionNames;
}
private static String createFunctionTemplate(Function function) {
StringBuilder sb = new StringBuilder(16);
sb.append(function.getName());
sb.append("(");
for (int i = 0; i < function.getNumArgs(); i++) {
if (i > 0) {
sb.append(",");
}
sb.append("@");
}
sb.append(")");
return sb.toString();
}
protected JPanel createDefaultAccessoryPane(Component subAssessory) {
JPanel patternPane = createPatternInsertionPane();
// JPanel historyPane = createHistoryPane();
// _actionPane = createActionPane();
JPanel p1 = new JPanel(new BorderLayout());
p1.add(new JLabel(" "), BorderLayout.NORTH);
p1.add(patternPane, BorderLayout.CENTER);
JPanel p2 = new JPanel(new BorderLayout(4, 4));
p2.add(p1, BorderLayout.NORTH);
// p2.add(historyPane, BorderLayout.CENTER);
// p2.add(_actionPane, BorderLayout.SOUTH);
if (subAssessory != null) {
JPanel p3 = new JPanel(new BorderLayout(4, 4));
p3.add(subAssessory, BorderLayout.WEST);
p3.add(p2, BorderLayout.EAST);
return p3;
} else {
return p1;
}
}
private static void add(JPanel panel, Component comp, GridBagConstraints gbc) {
final GridBagLayout gbl = (GridBagLayout) panel.getLayout();
gbl.setConstraints(comp, gbc);
panel.add(comp, gbc);
}
protected void checkCode() {
checkCode(getCode());
}
protected void checkCode(String code) {
lastErrorMessage = null;
String message;
Color foreground;
if ((code == null || code.trim().isEmpty())) {
if (emptyExpressionAllowed) {
return;
} else {
lastErrorMessage = "Empty expression not allowed."; /*I18N*/
message = lastErrorMessage;
foreground = warnMsgColor;
}
} else if (code.contains(PLACEHOLDER)) {
lastErrorMessage = "Replace '@' by inserting an element."; /*I18N*/
message = lastErrorMessage;
foreground = warnMsgColor;
} else {
if (parser != null) {
try {
Term term = parser.parse(code);
if (term != null && !BandArithmetic.areRastersEqualInSize(term)) {
message = "Referenced rasters must all be of the same size";
foreground = warnMsgColor;
} else if (term == null || !booleanExpressionPreferred || term.isB()) {
message = "Ok, no errors."; /*I18N*/
foreground = okMsgColor;
} else {
message = "Ok, but not a boolean expression."; /*I18N*/
foreground = warnMsgColor;
}
} catch (ParseException e) {
lastErrorMessage = e.getMessage();
message = lastErrorMessage;
foreground = warnMsgColor;
}
} else {
message = "Ok, no errors."; /*I18N*/
foreground = okMsgColor;
}
}
messageLabel.setText(message);
messageLabel.setToolTipText(message);
messageLabel.setForeground(foreground);
}
public String getLastErrorMessage() {
return lastErrorMessage;
}
protected void updateUIState() {
if (actionPane != null) {
actionPane.updateUIState();
}
}
private void pushCodeOnUndoStack(String code) {
if (undoBuffer.isEmpty() || !code.equals(undoBuffer.peek())) {
undoBuffer.push(code);
}
}
private static String getTypeString(int type) {
if (type == Term.TYPE_B) {
return "boolean";
} else if (type == Term.TYPE_I) {
return "int";
} else if (type == Term.TYPE_D) {
return "double";
} else {
return "?";
}
}
public static String getParamTypeString(String name, Term[] args) {
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append('(');
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(',');
}
sb.append(getTypeString(args[i].getRetType()));
}
sb.append(')');
return sb.toString();
}
private static String getFunctionCallPattern(Function function) {
String functionName = function.getName();
int numArgs = function.getNumArgs();
if (numArgs == -1) {
return function.getName() + "(@, @, ...)";
} else if (numArgs == 0) {
return function.getName() + "()";
} else {
functionName += function.getName() + "(@";
for (int j = 1; j < numArgs; j++) {
functionName += ", @";
}
return functionName + ")";
}
}
class ActionPane extends JPanel {
private AbstractButton selAllButton;
private AbstractButton undoButton;
private AbstractButton clearButton;
private AbstractButton historyUpButton;
private AbstractButton historyDownButton;
public ActionPane() {
createUI();
}
protected void createUI() {
selAllButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/SelectAll24.gif"), false);
selAllButton.setName("selAllButton");
selAllButton.setToolTipText("Select all");
selAllButton.addActionListener(e -> selectAllCode());
clearButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Remove24.gif"), false);
clearButton.setName("clearButton");
clearButton.setToolTipText("Clear");
clearButton.addActionListener(e -> clearCode());
undoButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Undo24.gif"), false);
undoButton.setName("undoButton");
undoButton.setToolTipText("Undo");
undoButton.addActionListener(e -> undoLastEdit());
historyUpButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/HistoryUp24.gif"), false);
historyUpButton.setName("historyUpButton");
historyUpButton.setToolTipText("Scroll history up");
historyUpButton.addActionListener(e -> {
if (history.size() > 0 && historyIndex < history.size()) {
historyIndex++;
setCode(history.get(historyIndex));
}
});
historyDownButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/HistoryDown24.gif"),
false);
historyDownButton.setName("historyDownButton");
historyDownButton.setToolTipText("Scroll history down");
historyDownButton.addActionListener(e -> {
if (history.size() > 0 && historyIndex >= 0) {
final int oldHistoryIndex = historyIndex;
historyIndex--;
setCode(history.get(oldHistoryIndex));
}
});
add(selAllButton, BorderLayout.WEST);
add(clearButton, BorderLayout.CENTER);
add(undoButton, BorderLayout.EAST);
add(historyUpButton);
add(historyDownButton);
}
protected void updateUIState() {
String text = codeArea.getText();
final boolean hasText = text.length() > 0;
final boolean canUndo = !undoBuffer.isEmpty();
final boolean hasHistory = history != null && !history.isEmpty();
selAllButton.setEnabled(hasText);
clearButton.setEnabled(hasText);
undoButton.setEnabled(canUndo);
historyUpButton.setEnabled(hasHistory && historyIndex < history.size() - 1);
historyDownButton.setEnabled(hasHistory && historyIndex >= 0);
}
}
class ExpressionPaneDialog extends ModalDialog {
public ExpressionPaneDialog(Window parent, String title) {
super(parent, title, ExpressionPane.this, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_HELP,
ExpressionPane.HELP_ID);
}
@Override
protected void onOK() {
updateCodeHistory();
super.onOK();
}
@Override
protected boolean verifyUserInput() {
checkCode();
String lastErrorMessage = getLastErrorMessage();
if (lastErrorMessage != null) {
JOptionPane.showMessageDialog(getJDialog(), lastErrorMessage, "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
return true;
}
}
}