Eliminate use of jcalendar. Replaced DateEditor with non-editable

display for Options use.
This commit is contained in:
ghidra1 2019-06-13 15:36:26 -04:00
parent 3b999fd86e
commit 8377f8394b
7 changed files with 11 additions and 718 deletions

View file

@ -1,277 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.bean.opteditor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.awt.Component;
import java.awt.Container;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.TreePath;
import org.junit.*;
import com.toedter.calendar.JCalendar;
import docking.action.DockingActionIf;
import docking.options.editor.*;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
public class DateEditorTest extends AbstractGhidraHeadedIntegrationTest {
private static final String TEST_CATEGORY_NAME = "Test Editor";
private static final String TEST_DATE_OPTION_NAME = "Time Option Name";
private TestEnv env;
private PluginTool tool;
private Program program;
private CodeBrowserPlugin plugin;
@Before
public void setUp() throws Exception {
program = buildProgram();
env = new TestEnv();
tool = env.showTool(program);
tool.addPlugin(CodeBrowserPlugin.class.getName());
plugin = env.getPlugin(CodeBrowserPlugin.class);
}
private Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
builder.createMemory("test1", Long.toHexString(0x1001000), 0x2000);
return builder.getProgram();
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testEditor() throws Exception {
Date initialDate = new Date(System.currentTimeMillis());
DateEditor dateEditor = addDateProperty(initialDate);
showProgramOptions();
OptionsDialog optionsDialog = waitForDialogComponent(OptionsDialog.class);
ScrollableOptionsEditor optionsEditor = selectionDateOptionCategory(optionsDialog);
Component c = findPairedComponent(optionsEditor, TEST_DATE_OPTION_NAME);
assertNotNull(c);
JTextField optionTextField = findComponent((Container) c, JTextField.class);
assertNotNull(optionTextField);
String testDateString = dateEditor.format(initialDate);
assertEquals(testDateString, getText(optionTextField));
JDialog dateDialog = lauchDateEditorDialog(c);
JCalendar calendar = findComponent(dateDialog, JCalendar.class);
assertNotNull("Could not find JCalendar", calendar);
Calendar cal = calendar.getCalendar();
int hours = runSwing(() -> cal.get(Calendar.HOUR_OF_DAY));
int minutes = runSwing(() -> cal.get(Calendar.MINUTE));
int seconds = runSwing(() -> cal.get(Calendar.SECOND));
JTextField hoursField = getTextField(dateDialog, "Hours");
JTextField minutesField = getTextField(dateDialog, "Minutes");
JTextField secondsField = getTextField(dateDialog, "Seconds");
assertEquals(hours, Integer.parseInt(getText(hoursField)));
assertEquals(minutes, Integer.parseInt(getText(minutesField)));
assertEquals(seconds, Integer.parseInt(getText(secondsField)));
JCalendar cd = calendar;
Date currentDate = runSwing(() -> cd.getCalendar().getTime());
assertDateLabelValue(dateDialog, currentDate);
// change the time
setText(hoursField, "01");
setText(minutesField, "21");
setText(secondsField, "59");
Calendar c2 = (Calendar) cal.clone();
runSwing(() -> {
c2.set(Calendar.HOUR_OF_DAY, 1);
c2.set(Calendar.MINUTE, 21);
c2.set(Calendar.SECOND, 59);
cd.setCalendar(c2);
});
Date newDate = runSwing(() -> c2.getTime());
pressOk(dateDialog);
// make sure the text field was updated
testDateString = dateEditor.format(newDate);
assertEquals(testDateString, getText(optionTextField));
pressApply(optionsDialog);
// make sure the property changed
Options plist = program.getOptions(TEST_CATEGORY_NAME);
Date actualDate = plist.getDate(TEST_DATE_OPTION_NAME, (Date) null);
assertEquals(newDate, actualDate);
closeAllWindowsAndFrames();
}
private void assertDateLabelValue(Container c, Date date) {
JLabel dateLabel = (JLabel) findComponentByName(c, "DateString");
assertNotNull(dateLabel);
SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy ");
assertEquals(formatter.format(date), runSwing(() -> dateLabel.getText()));
}
private JTextField getTextField(Container c, String name) {
JTextField tf = (JTextField) findComponentByName(c, name);
assertNotNull(tf);
return tf;
}
private ScrollableOptionsEditor selectionDateOptionCategory(OptionsDialog optionsDialog)
throws Exception {
OptionsPanel optionsPanel = (OptionsPanel) getInstanceField("panel", optionsDialog);
Container pane = optionsDialog.getComponent();
GTree tree = findComponent(pane, GTree.class);
waitForTree(tree);
GTreeNode testNode = getGTreeNode(tree.getRootNode(), TEST_CATEGORY_NAME);
selectNode(testNode);
ScrollableOptionsEditor p =
(ScrollableOptionsEditor) getEditorPanel(optionsPanel, testNode);
assertNotNull(p);
return p;
}
private void pressApply(OptionsDialog optionsDialog) {
JButton applyButton = findButtonByText(optionsDialog.getComponent(), "Apply");
pressButton(applyButton);
waitForSwing();
}
private void pressOk(JDialog dateDialog) {
JButton okButton = findButtonByText(dateDialog.getContentPane(), "OK");
assertNotNull(okButton);
pressButton(okButton);
waitForSwing();
}
private JDialog lauchDateEditorDialog(Component c) {
JButton button = findButtonByIcon((Container) c, ButtonPanelFactory.BROWSE_ICON);
assertNotNull(button);
pressButton(button, false);
waitForSwing();
JDialog dateDialog = waitForJDialog("Edit Date");
assertNotNull(dateDialog);
return dateDialog;
}
private void showProgramOptions() {
// TODO change to getAction("Program Options")
Set<DockingActionIf> list = tool.getAllActions();
for (DockingActionIf action : list) {
if (action.getName().equals("Program Options")) {
performAction(action, plugin.getProvider(), false);
break;
}
}
waitForSwing();
}
private Object getEditorPanel(OptionsPanel optionsPanel, Object testNode) {
Map<?, ?> map = (Map<?, ?>) getInstanceField("editorMap", optionsPanel);
return map.get(testNode);
}
private void selectNode(GTreeNode node) throws Exception {
TreePath path = node.getTreePath();
GTree tree = node.getTree();
tree.setSelectionPath(path);
waitForTree(tree);
}
private GTreeNode getGTreeNode(GTreeNode parent, String nodeName) throws Exception {
for (int i = 0; i < parent.getChildCount(); i++) {
GTreeNode node = parent.getChild(i);
if (node.getName().equals(nodeName)) {
return node;
}
GTreeNode foundNode = getGTreeNode(node, nodeName);
if (foundNode != null) {
return foundNode;
}
}
return null;
}
private DateEditor addDateProperty(Date date) {
Options list = program.getOptions(TEST_CATEGORY_NAME);
list.registerOption(TEST_DATE_OPTION_NAME, new Date(0), null, "Test for the DateEditor");
int transactionID = program.startTransaction("My Test");
try {
list.setDate(TEST_DATE_OPTION_NAME, date);
}
finally {
program.endTransaction(transactionID, true);
}
return (DateEditor) runSwing(() -> list.getPropertyEditor(TEST_DATE_OPTION_NAME));
}
private Component findPairedComponent(Container container, String labelText) {
Component[] c = container.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof JLabel) {
if (((JLabel) c[i]).getText().equals(labelText)) {
return c[i + 1];
}
}
if (c[i] instanceof Container) {
Component comp = findPairedComponent((Container) c[i], labelText);
if (comp != null) {
return comp;
}
}
}
return null;
}
}

View file

@ -1,3 +1,2 @@
MODULE FILE LICENSE: lib/timingframework-1.0.jar BSD
MODULE FILE LICENSE: lib/jcalendar-1.4.jar LGPL 2.1
MODULE FILE LICENSE: lib/javahelp-2.0.05.jar GPL 2 With Classpath Exception

View file

@ -9,8 +9,6 @@ eclipse.project.name = 'Framework Docking'
dependencies {
compile project(':Generic')
compile 'net.java.dev.timingframework:timingframework:1.0'
compile 'com.toedter:jcalendar:1.4'
// Only include this debug version of the jh library if necessary.
//compile name:'jh2.with.debug'

View file

@ -1,302 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.options.editor;
import java.awt.Color;
import java.awt.Toolkit;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import com.toedter.calendar.JCalendar;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import ghidra.util.layout.HorizontalLayout;
/**
* A simple set of label and text fields to show the current time and allow for editing
*/
class Clock extends JPanel implements CaretListener {
private JLabel dateLabel = new GDLabel("Apr 18, 2006");
private JTextField hoursField;
private JTextField minutesField;
private JTextField secondsField;
private SimpleDateFormat formatter;
private DocumentListener docListener;
private boolean isEditing;
private JTextField currentEditField;
private int min;
private int max;
private int value;
private Color defaultBackgroundColor;
private static Color CURRENT_BACKGROUND_COLOR = new Color(204, 204, 255);
private JCalendar jCalendar;
/**
* Construct a new Clock.
* @param calendar JCalendar component that has the Calendar object being edited.
*/
Clock(JCalendar calendar) {
super(new HorizontalLayout(1));
this.jCalendar = calendar;
setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
create();
}
@Override
public void caretUpdate(CaretEvent e) {
JTextField tf = (JTextField) e.getSource();
Document doc = tf.getDocument();
max = 59;
if (hoursField.getDocument() == doc) {
max = 23;
currentEditField = hoursField;
updateColors(minutesField);
updateColors(secondsField);
}
else if (minutesField.getDocument() == doc) {
currentEditField = minutesField;
updateColors(hoursField);
updateColors(secondsField);
}
else {
currentEditField = secondsField;
updateColors(hoursField);
updateColors(minutesField);
}
currentEditField.setBackground(CURRENT_BACKGROUND_COLOR);
}
/*
* Update the text fields that show the time.
*/
void update(Date date) {
if (isEditing) {
return;
}
removeDocumentListeners();
try {
String dateStr = formatter.format(date);
dateLabel.setText(getDate(dateStr) + " ");
hoursField.setText(getHours(dateStr));
minutesField.setText(getMinutes(dateStr));
secondsField.setText(getSeconds(dateStr));
}
finally {
addDocumentListeners();
}
if (currentEditField != null) {
setValue(Integer.parseInt(currentEditField.getText()), false);
}
}
private void create() {
hoursField = new JTextField(2);
minutesField = new JTextField(2);
secondsField = new JTextField(2);
hoursField.addCaretListener(this);
minutesField.addCaretListener(this);
secondsField.addCaretListener(this);
// junit access
hoursField.setName("Hours");
minutesField.setName("Minutes");
secondsField.setName("Seconds");
dateLabel.setName("DateString");
min = 0;
max = 60;
value = 0;
add(dateLabel);
add(hoursField);
add(new GLabel(":"));
add(minutesField);
add(new GLabel(":"));
add(secondsField);
formatter = new SimpleDateFormat("HH:mm:ss MMM dd, yyyy");
hoursField.setDocument(new TimeDocument(true));
minutesField.setDocument(new TimeDocument(false));
secondsField.setDocument(new TimeDocument(false));
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
update(date);
docListener = new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
checkInput(e);
}
@Override
public void insertUpdate(DocumentEvent e) {
checkInput(e);
}
@Override
public void changedUpdate(DocumentEvent e) {
checkInput(e);
}
};
addDocumentListeners();
defaultBackgroundColor = hoursField.getBackground();
}
private void addDocumentListeners() {
hoursField.getDocument().addDocumentListener(docListener);
minutesField.getDocument().addDocumentListener(docListener);
secondsField.getDocument().addDocumentListener(docListener);
}
private void removeDocumentListeners() {
hoursField.getDocument().removeDocumentListener(docListener);
minutesField.getDocument().removeDocumentListener(docListener);
secondsField.getDocument().removeDocumentListener(docListener);
}
private void checkInput(DocumentEvent e) {
isEditing = true;
Calendar cal = jCalendar.getCalendar();
try {
int hourValue = Integer.parseInt(hoursField.getText());
int minutesValue = Integer.parseInt(minutesField.getText());
int secondsValue = Integer.parseInt(secondsField.getText());
if (hourValue >= 0 && hourValue <= 23 && minutesValue >= 0 && minutesValue <= 59 &&
secondsValue >= 0 && secondsValue <= 59) {
cal.set(Calendar.HOUR_OF_DAY, hourValue);
cal.set(Calendar.MINUTE, minutesValue);
cal.set(Calendar.SECOND, secondsValue);
}
}
catch (NumberFormatException exc) {
// this can happen for the empty string
}
finally {
isEditing = false;
}
}
private void updateColors(JTextField field) {
field.setBackground(defaultBackgroundColor);
}
private void setValue(int newValue, boolean updateTextField) {
if (newValue < min) {
value = min;
}
else if (newValue > max) {
value = max;
}
else {
value = newValue;
}
if (updateTextField) {
String str = Integer.toString(value);
if (str.length() < 2) {
str = "0" + str;
}
currentEditField.setText(str);
}
}
private String getHours(String dateStr) {
int pos = dateStr.indexOf(":");
return dateStr.substring(0, pos);
}
private String getMinutes(String dateStr) {
return dateStr.substring(3, 5);
}
private String getSeconds(String dateStr) {
return dateStr.substring(6, 8);
}
private String getDate(String dateStr) {
int pos = dateStr.indexOf(" ");
return dateStr.substring(pos + 1);
}
private class TimeDocument extends PlainDocument {
private boolean isHours;
TimeDocument(boolean isHours) {
super();
this.isHours = isHours;
}
@Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
char[] source = str.toCharArray();
char[] result = new char[source.length];
int j = 0;
for (int i = 0; i < result.length; i++) {
if (Character.isDigit(source[i]) && isValidDigit(source[i])) {
result[j++] = source[i];
}
else {
Toolkit.getDefaultToolkit().beep();
}
String s = this.getText(0, getLength());
try {
if (s.length() > 0) {
if (offs == 0) {
s = str + s;
}
else {
s = s + str;
}
int numericValue = Integer.parseInt(s);
if (s.length() > 2 ||
(isHours && (numericValue < 0 || numericValue > 23)) ||
(!isHours && (numericValue < 0 || numericValue >= 60))) {
Toolkit.getDefaultToolkit().beep();
return;
}
}
}
catch (NumberFormatException e) {
Toolkit.getDefaultToolkit().beep();
return;
}
}
super.insertString(offs, new String(result, 0, j), a);
}
private boolean isValidDigit(char c) {
return c >= 0x30 && c <= 0x39;
}
}
}

View file

@ -15,23 +15,15 @@
*/
package docking.options.editor;
import java.awt.*;
import java.beans.*;
import java.awt.Component;
import java.beans.PropertyEditorSupport;
import java.text.*;
import java.util.Calendar;
import java.util.Date;
import javax.swing.*;
import com.toedter.calendar.JCalendar;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
/**
* Editor for date and time; creates a text field for the string version of the date, and
* a browse button to pop up the editor panel that contains a JCalendar to edit the date,
* and a separate component to edit the time.
* Non-editable Editor for date and time; creates a text field for the string version of the date.
*/
public class DateEditor extends PropertyEditorSupport {
@ -40,9 +32,7 @@ public class DateEditor extends PropertyEditorSupport {
private final static int NUMBER_OF_COLUMNS = 20;
private Date date;
private CalendarPanel calendarPanel;
private JTextField textField;
private EditDateDialog dialog;
private DateFormat dateFormat = DEFAULT_DATE_FORMAT;
public void setDateFormat(DateFormat format) {
@ -81,10 +71,6 @@ public class DateEditor extends PropertyEditorSupport {
this.date = (Date) value;
if (calendarPanel != null) {
calendarPanel.setDate(date);
}
if (textField != null) {
textField.setText(format(date));
}
@ -101,19 +87,6 @@ public class DateEditor extends PropertyEditorSupport {
}
}
private void displayCalendarEditor(Component parent) {
if (calendarPanel == null) {
calendarPanel = new CalendarPanel();
}
if (date != null) {
calendarPanel.setDate(date);
}
if (dialog == null) {
dialog = new EditDateDialog(parent);
}
DockingWindowManager.showDialog(parent, dialog);
}
private class DatePanel extends JPanel {
private JButton browseButton;
@ -123,101 +96,9 @@ public class DateEditor extends PropertyEditorSupport {
textField = new JTextField(NUMBER_OF_COLUMNS);
textField.setText(date != null ? format(date) : "");
textField.setEditable(false);
browseButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
Font f = browseButton.getFont();
f = new Font(f.getName(), Font.BOLD, f.getSize());
browseButton.setFont(f);
add(textField);
add(Box.createHorizontalStrut(5));
add(browseButton);
setBorder(BorderFactory.createEmptyBorder());
browseButton.addActionListener(e -> displayCalendarEditor(browseButton));
}
}
private class CalendarPanel extends JPanel implements PropertyChangeListener {
private JCalendar jcal;
private Clock clock;
// this formatter is just used for parsing dates for input into JCalendar
private SimpleDateFormat formatter;
CalendarPanel() {
super(new BorderLayout(0, 5));
setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
jcal = new JCalendar();
jcal.addPropertyChangeListener(this);
clock = new Clock(jcal);
add(jcal, BorderLayout.CENTER);
add(clock, BorderLayout.SOUTH);
formatter = new SimpleDateFormat("MM dd yyyy");
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (propertyName == null || propertyName.equals("calendar")) {
Calendar newCal = (Calendar) evt.getNewValue();
Date newDate = newCal.getTime();
clock.update(newDate);
}
}
@Override
public Dimension getPreferredSize() {
return super.getPreferredSize();
}
void setDate(Date date) {
Calendar calendar = jcal.getCalendar();
Calendar c = (Calendar) calendar.clone();
//need the month,day, year from the date
String dateString = formatter.format(date);
int month = Integer.parseInt(dateString.substring(0, 2));
int day = Integer.parseInt(dateString.substring(3, 5));
int year = Integer.parseInt(dateString.substring(6));
c.set(Calendar.MONTH, month);
c.set(Calendar.DAY_OF_WEEK, day);
c.set(Calendar.YEAR, year);
c.setTime(date);
jcal.setCalendar(c);
clock.update(date);
}
}
private class EditDateDialog extends DialogComponentProvider {
EditDateDialog(Component parent) {
super("Edit Date", true);
JPanel dialogPanel = new JPanel();
dialogPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
dialogPanel.setLayout(new BorderLayout());
dialogPanel.add(calendarPanel, BorderLayout.CENTER);
addWorkPanel(dialogPanel);
addOKButton();
addCancelButton();
}
/**
* Gets called when the user clicks on the OK Action for the dialog.
*/
@Override
protected void okCallback() {
Date newDate = calendarPanel.jcal.getCalendar().getTime();
DateEditor.this.setValue(newDate);
DateEditor.this.firePropertyChange();
close();
}
@Override
protected void cancelCallback() {
close();
}
}
}

View file

@ -27,7 +27,7 @@ public class EditorState implements PropertyChangeListener {
private Object originalValue;
private Object currentValue;
private PropertyEditor editor;
private Set<PropertyChangeListener> listeners = new HashSet<PropertyChangeListener>();
private Set<PropertyChangeListener> listeners = new HashSet<>();
private Options options;
private String name;
@ -37,10 +37,12 @@ public class EditorState implements PropertyChangeListener {
this.currentValue = options.getObject(name, null);
this.originalValue = currentValue;
this.editor = options.getPropertyEditor(name);
editor.setValue(currentValue);
if (editor != null) {
editor.setValue(currentValue);
editor.removePropertyChangeListener(this); // don't repeatedly add editors
editor.addPropertyChangeListener(this);
editor.removePropertyChangeListener(this); // don't repeatedly add editors
editor.addPropertyChangeListener(this);
}
}
void addListener(PropertyChangeListener listener) {
@ -143,9 +145,8 @@ public class EditorState implements PropertyChangeListener {
}
editor.removePropertyChangeListener(this);
editor =
new ErrorPropertyEditor("Ghidra does not know how to use PropertyEditor: " +
editor.getClass().getName(), null);
editor = new ErrorPropertyEditor(
"Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName(), null);
return editor.getCustomEditor();
}

View file

@ -180,13 +180,6 @@ task assembleDistribution (type: Copy) {
include "Icons/**"
include "licenses/**"
into "GPL"
}
//////////////////////////////
// LGPL SOURCE INCLUSION
//////////////////////////////
from ("${rootProject.ext.BIN_REPO}/ExternalLibraries/libsSrc/jcalendar-1.3.3.zip" ) {
into "GPL/librarySourceForLGPL"
}
//////////////////////////////