Merge branch '62-psp' into 'master'

Fix PropertySheetPanel behaviour

This PR cleans up the PropertySheetPanel and fixes some behaviour when showing/hiding properties based on their descriptor.

See merge request !13
This commit is contained in:
Fabian Becker 2015-12-23 02:47:45 +01:00
commit 28f442af50

View File

@ -15,7 +15,8 @@ import java.awt.event.MouseEvent;
import java.beans.*; import java.beans.*;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.EventObject; import java.util.*;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -49,11 +50,6 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
* Stores GUI components containing each editing component. * Stores GUI components containing each editing component.
*/ */
private JComponent views[]; private JComponent views[];
private JComponent viewWrappers[];
/**
* The labels for each property.
*/
private JLabel propertyLabels[];
/** /**
* The tool tip text for each property. * The tool tip text for each property.
*/ */
@ -189,6 +185,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
propertyTableModel = new DefaultTableModel(); propertyTableModel = new DefaultTableModel();
propertyTableModel.addColumn("Property"); propertyTableModel.addColumn("Property");
propertyTableModel.addColumn("Value"); propertyTableModel.addColumn("Value");
propertyTable = new ToolTipTable(propertyTableModel); propertyTable = new ToolTipTable(propertyTableModel);
propertyTable.setDefaultRenderer(Object.class, new PropertyCellRenderer()); propertyTable.setDefaultRenderer(Object.class, new PropertyCellRenderer());
propertyTable.setDefaultEditor(Object.class, new PropertyCellEditor()); propertyTable.setDefaultEditor(Object.class, new PropertyCellEditor());
@ -197,6 +194,18 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
propertyTable.setGridColor(Color.LIGHT_GRAY); propertyTable.setGridColor(Color.LIGHT_GRAY);
propertyTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); propertyTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
// This defines a new TableRowFilter - we hide rows where the component is not visible (hidden)
RowFilter<DefaultTableModel, Object> filter = new RowFilter<DefaultTableModel, Object>() {
@Override
public boolean include(Entry<? extends DefaultTableModel, ?> entry) {
JComponent comp = (JComponent)entry.getValue(1);
return comp.isVisible();
}
};
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(propertyTableModel);
sorter.setRowFilter(filter);
propertyTable.setRowSorter(sorter);
// Close any child windows at this point // Close any child windows at this point
removeAll(); removeAll();
setLayout(new GridBagLayout()); setLayout(new GridBagLayout());
@ -234,34 +243,26 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
} }
} }
int methsFound = 0; // don't loop too long, so count until all found // Allow object to mark properties hidden
for (MethodDescriptor methodDescriptor : methodDescriptors) { BeanInspector.callIfAvailable(targ, "hideHideable", null);
String name = methodDescriptor.getDisplayName();
Method meth = methodDescriptor.getMethod(); // Allow object to reorder properties
if (name.equals("hideHideable")) { Object retV = BeanInspector.callIfAvailable(targ, "customPropertyOrder", null);
Object args[] = {}; if (retV != null) {
try { if (retV.getClass().isArray()) {
meth.invoke(targetObject, args); reorderProperties((String[]) retV);
} catch (Exception ex) { } else {
} LOGGER.severe("Class defines custom property order but returns invalid type.");
methsFound++;
} else if (name.equals("customPropertyOrder")) {
methsFound++;
reorderProperties(meth);
}
if (methsFound == 2) {
break; // small speed-up
} }
} }
// Now lets search for the individual properties, their // Now lets search for the individual properties, their
// values, views and editors... // values, views and editors...
propertyEditors = new PropertyEditor[propertyDescriptors.length]; propertyEditors = new PropertyEditor[propertyDescriptors.length];
// collect property values if possible // collect property values if possible
objectValues = getValues(targetObject, propertyDescriptors, true, true, true); objectValues = getValues(targetObject, propertyDescriptors, true, false, true);
views = new JComponent[propertyDescriptors.length]; views = new JComponent[propertyDescriptors.length];
viewWrappers = new JComponent[propertyDescriptors.length];
propertyLabels = new JLabel[propertyDescriptors.length];
toolTips = new String[propertyDescriptors.length]; toolTips = new String[propertyDescriptors.length];
int itemIndex = 0; int itemIndex = 0;
@ -273,7 +274,6 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
if (objectValues[i] == null) { if (objectValues[i] == null) {
continue; // expert, hidden, or no getter/setter available continue; // expert, hidden, or no getter/setter available
} }
JComponent newView;
try { try {
propertyEditors[i] = makeEditor(propertyDescriptors[i], name, objectValues[i]); propertyEditors[i] = makeEditor(propertyDescriptors[i], name, objectValues[i]);
@ -289,8 +289,10 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
toolTips[itemIndex] = BeanInspector.getToolTipText(name, methodDescriptors, targetObject); toolTips[itemIndex] = BeanInspector.getToolTipText(name, methodDescriptors, targetObject);
} }
itemIndex++; itemIndex++;
newView = getView(propertyEditors[i]); views[i] = getView(propertyEditors[i]);
if (newView == null) { // We filter by this.. not necessarily the prettiest solution but it tends to work
views[i].setVisible(!propertyDescriptors[i].isHidden());
if (views[i] == null) {
LOGGER.warning("Warning: Property \"" + name + "\" has non-displayable editor. Skipping."); LOGGER.warning("Warning: Property \"" + name + "\" has non-displayable editor. Skipping.");
continue; continue;
} }
@ -300,7 +302,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
continue; continue;
} // end try } // end try
propertyTableModel.addRow(new Object[]{prepareLabel(name), newView}); propertyTableModel.addRow(new Object[]{prepareLabel(name), views[i]});
} }
propertyTable.setToolTips(toolTips); propertyTable.setToolTips(toolTips);
@ -324,7 +326,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
setVisible(true); setVisible(true);
} }
private String prepareLabel(String label) { private static String prepareLabel(String label) {
// Add some specific display for some greeks here // Add some specific display for some greeks here
label = StringTools.translateGreek(label); label = StringTools.translateGreek(label);
label = StringTools.humaniseCamelCase(label); label = StringTools.humaniseCamelCase(label);
@ -425,7 +427,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
// If it's a user-defined property we give a warning. // If it's a user-defined property we give a warning.
String getterClass = property.getReadMethod().getDeclaringClass().getName(); String getterClass = property.getReadMethod().getDeclaringClass().getName();
if (getterClass.indexOf("java.") != 0) { if (getterClass.indexOf("java.") != 0) {
System.out.println("Warning: Property \"" + name + "\" of class " + targetObject.getClass() + " has null initial value. Skipping."); LOGGER.warning("Warning: Property \"" + name + "\" of class " + targetObject.getClass() + " has null initial value. Skipping.");
} }
return null; return null;
} }
@ -438,38 +440,30 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
/** /**
* Be sure to give a clone * Be sure to give a clone
* *
* @param meth * @param swProps
* @return * @return
*/ */
private PropertyDescriptor[] reorderProperties(Method meth) { private PropertyDescriptor[] reorderProperties(String[] swProps) {
Object[] args = {}; Object[] args = {};
Object retV = null;
PropertyDescriptor[] newProps = null; PropertyDescriptor[] newProps = null;
try { if (swProps != null) {
retV = meth.invoke(targetObject, args); // should return String[] to be interpreted as a list of ordered properties
} catch (Exception ex) {
}
if (retV != null) {
try { try {
if (retV.getClass().isArray()) { // reorder the properties PropertyDescriptor[] oldProps = propertyDescriptors.clone();
String[] swProps = (String[]) retV; newProps = new PropertyDescriptor[oldProps.length];
PropertyDescriptor[] oldProps = propertyDescriptors.clone(); //int findFirst=findFirstProp(props[0], oldProps);
newProps = new PropertyDescriptor[oldProps.length]; int firstNonNull = 0;
//int findFirst=findFirstProp(props[0], oldProps); for (int i = 0; i < oldProps.length; i++) {
int firstNonNull = 0; if (i < swProps.length) {
for (int i = 0; i < oldProps.length; i++) { int pInOld = findProp(oldProps, swProps[i]);
if (i < swProps.length) { newProps[i] = oldProps[pInOld];
int pInOld = findProp(oldProps, swProps[i]); oldProps[pInOld] = null;
newProps[i] = oldProps[pInOld]; } else {
oldProps[pInOld] = null; firstNonNull = findFirstNonNullAfter(oldProps, firstNonNull);
} else { newProps[i] = oldProps[firstNonNull];
firstNonNull = findFirstNonNullAfter(oldProps, firstNonNull); firstNonNull++;
newProps[i] = oldProps[firstNonNull];
firstNonNull++;
}
} }
propertyDescriptors = newProps;
} }
propertyDescriptors = newProps;
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error during reordering properties: " + e.getMessage()); System.err.println("Error during reordering properties: " + e.getMessage());
return propertyDescriptors; return propertyDescriptors;
@ -584,12 +578,10 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
Method getter = propertyDescriptors[i].getReadMethod(); Method getter = propertyDescriptors[i].getReadMethod();
objectValues[i] = newValue; objectValues[i] = newValue;
Method setter = property.getWriteMethod(); Method setter = property.getWriteMethod();
// @todo: Streiche so something was changed, i could check if i have to change the editor
PropertyEditor tmpEdit = null;
// the findEditor method using properties may retrieve a primitive editor, the other one, for obscure reasons, cant. // the findEditor method using properties may retrieve a primitive editor, the other one, for obscure reasons, cant.
// so Ill use the mightier first. // so Ill use the mightier first.
tmpEdit = PropertyEditorProvider.findEditor(propertyDescriptors[i], newValue); PropertyEditor tmpEdit = PropertyEditorProvider.findEditor(propertyDescriptors[i], newValue);
if (tmpEdit == null) { if (tmpEdit == null) {
tmpEdit = PropertyEditorProvider.findEditor(propertyDescriptors[i].getPropertyType()); tmpEdit = PropertyEditorProvider.findEditor(propertyDescriptors[i].getPropertyType());
} }
@ -600,8 +592,8 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
((GenericObjectEditor) tmpEdit).setClassType(propertyDescriptors[i].getPropertyType()); ((GenericObjectEditor) tmpEdit).setClassType(propertyDescriptors[i].getPropertyType());
} }
propertyEditors[i].setValue(newValue); propertyEditors[i].setValue(newValue);
JComponent newView = null;
newView = getView(tmpEdit); JComponent newView = getView(tmpEdit);
if (newView == null) { if (newView == null) {
LOGGER.warning("Property \"" + propertyDescriptors[i].getDisplayName() + "\" has non-displayable editor. Skipping."); LOGGER.warning("Property \"" + propertyDescriptors[i].getDisplayName() + "\" has non-displayable editor. Skipping.");
return false; return false;
@ -611,10 +603,6 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
if (toolTips[i] != null) { if (toolTips[i] != null) {
views[i].setToolTipText(toolTips[i]); views[i].setToolTipText(toolTips[i]);
} }
viewWrappers[i].removeAll();
viewWrappers[i].setLayout(new BorderLayout());
viewWrappers[i].add(views[i], BorderLayout.CENTER);
viewWrappers[i].repaint();
} }
// Now try to update the target with the new value of the property // Now try to update the target with the new value of the property
@ -670,7 +658,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
for (int i = 0; i < propertyEditors.length; i++) { for (int i = 0; i < propertyEditors.length; i++) {
if (propertyEditors[i] == editor) { if (propertyEditors[i] == editor) {
propIndex = i; propIndex = i;
if (wasModified(i, editor.getValue(), true)) { if (wasModified(i, editor.getValue())) {
break; break;
} }
} }
@ -688,13 +676,16 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
* propertysheet?). * propertysheet?).
* *
*/ */
synchronized boolean wasModified(int propIndex, Object value, boolean followDependencies) { synchronized boolean wasModified(int propIndex, Object value) {
if (!updateValue(propIndex, value)) { if (!updateValue(propIndex, value)) {
return false; return false;
} }
boolean doRepaint = false; boolean doRepaint = false;
BeanInspector.callIfAvailable(this.targetObject, "hideHideable", null);
// ToDo Should be foreach (to skip non existing editors)
for (int i = 0; i < propertyEditors.length; i++) { // check the views for out-of-date information. this is different than checking the editors for (int i = 0; i < propertyEditors.length; i++) { // check the views for out-of-date information. this is different than checking the editors
if (i != propIndex) { if (i != propIndex) {
if (updateFieldView(i)) { if (updateFieldView(i)) {
@ -703,22 +694,19 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
}// end if (editorTable[i] == editor) { }// end if (editorTable[i] == editor) {
} // end for (int i = 0 ; i < editorTable.length; i++) { } // end for (int i = 0 ; i < editorTable.length; i++) {
if (doRepaint) { // some components have been hidden or reappeared if (doRepaint) { // some components have been hidden or reappeared
// MK this finally seems to work right, with a scroll pane, too. propertyTable.getRowSorter().allRowsChanged();
Container p = this;
while (p != null && (!p.getSize().equals(p.getPreferredSize()))) {
p.setSize(p.getPreferredSize());
p = p.getParent();
}
} }
// Now re-read all the properties and update the editors // Now re-read all the properties and update the editors
// for any other properties that have changed. // for any other properties that have changed.
for (int i = 0; i < propertyDescriptors.length; i++) { for (int i = 0; i < propertyDescriptors.length; i++) {
Object o; Object o;
Method getter = null; Method getter;
// Make sure we have an editor for this property...
if (propertyEditors[i] == null) { if (propertyEditors[i] == null) {
continue; /// TODO: MK: Im not quite sure this is all good, but it avoids a latency problem continue;
} }
try { try {
getter = propertyDescriptors[i].getReadMethod(); getter = propertyDescriptors[i].getReadMethod();
Object args[] = {}; Object args[] = {};
@ -728,6 +716,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
System.err.println(ex.getMessage()); System.err.println(ex.getMessage());
ex.printStackTrace(); ex.printStackTrace();
} }
if ((o != null) && o == objectValues[i] && (BeanInspector.isJavaPrimitive(o.getClass()))) { if ((o != null) && o == objectValues[i] && (BeanInspector.isJavaPrimitive(o.getClass()))) {
// The property is equal to its old value. // The property is equal to its old value.
continue; continue;
@ -737,10 +726,7 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
continue; continue;
} }
objectValues[i] = o; objectValues[i] = o;
// Make sure we have an editor for this property...
if (propertyEditors[i] == null) {
continue;
}
// The property has changed! Update the editor. // The property has changed! Update the editor.
propertyEditors[i].removePropertyChangeListener(this); propertyEditors[i].removePropertyChangeListener(this);
propertyEditors[i].setValue(o); propertyEditors[i].setValue(o);
@ -751,17 +737,6 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
} }
} }
if (followDependencies) {
// Handle the special method getGOEPropertyUpdateLinks which returns a list of pairs
// of strings indicating that on an update of the i-th property, the i+1-th property
// should be updated. This is useful for changes within sub-classes of the target
// which are not directly displayed in this panel but in sub-panels (and there have an own view etc.)
Object o = BeanInspector.callIfAvailable(targetObject, "getGOEPropertyUpdateLinks", null);
if ((o != null) && (o instanceof String[])) {
maybeTriggerUpdates(propIndex, (String[]) o);
}
}
// Make sure the target bean gets repainted. // Make sure the target bean gets repainted.
if (Beans.isInstanceOf(targetObject, Component.class)) { if (Beans.isInstanceOf(targetObject, Component.class)) {
//System.out.println("Beans.getInstanceOf repaint "); //System.out.println("Beans.getInstanceOf repaint ");
@ -778,26 +753,22 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
* @return * @return
*/ */
private boolean updateFieldView(int i) { private boolean updateFieldView(int i) {
// looking at another field (not changed explicitly, maybe implicitly // looking at another field (not changed explicitly, maybe implicitly)
boolean valChanged = false; boolean valChanged;
boolean doRepaint = false; boolean doRepaint = false;
Object args[] = {}; Object args[] = {};
Method getter = propertyDescriptors[i].getReadMethod(); Method getter = propertyDescriptors[i].getReadMethod();
if (propertyDescriptors[i].isHidden() || propertyDescriptors[i].isExpert()) { if (propertyDescriptors[i].isHidden() || propertyDescriptors[i].isExpert()) {
if ((propertyLabels[i] != null) && (propertyLabels[i].isVisible())) { if ((views[i] != null) && (views[i].isVisible())) {
// something is set to hidden but was visible up to now // something is set to hidden but was visible up to now
viewWrappers[i].setVisible(false);
views[i].setVisible(false); views[i].setVisible(false);
propertyLabels[i].setVisible(false);
doRepaint = true; doRepaint = true;
} }
return doRepaint; return doRepaint;
} else { } else {
if ((propertyLabels[i] != null) && !(propertyLabels[i].isVisible())) { if ((views[i] != null) && !(views[i].isVisible())) {
// something is invisible but set to not hidden in the mean time // something is invisible but set to not hidden in the mean time
viewWrappers[i].setVisible(true);
views[i].setVisible(true); views[i].setVisible(true);
propertyLabels[i].setVisible(true);
doRepaint = true; doRepaint = true;
} }
} }
@ -830,46 +801,6 @@ public final class PropertySheetPanel extends JPanel implements PropertyChangeLi
} }
return doRepaint; return doRepaint;
} }
/**
* Check the given link list and trigger updates of indicated properties.
*
* @param propIndex
* @param links
*/
private void maybeTriggerUpdates(int propIndex, String[] links) {
int max = links.length;
if (max % 2 == 1) {
System.err.println("Error in PropertySheetPanel:maybeTriggerUpdates: odd number of strings provided!");
max -= 1;
}
for (int i = 0; i < max; i += 2) {
if (links[i].equals(propertyDescriptors[propIndex].getName())) {
updateLinkedProperty(links[i + 1]);
}
}
}
private void updateLinkedProperty(String propName) {
for (int i = 0; i < propertyDescriptors.length; i++) {
if (propertyDescriptors[i].getName().equals(propName)) {
Method getter = propertyDescriptors[i].getReadMethod();
Object val = null;
try {
val = getter.invoke(targetObject, (Object[]) null);
} catch (Exception e) {
val = null;
e.printStackTrace();
}
if (val != null) {
propertyEditors[i].setValue(val);
} else {
System.err.println("Error in PropertySheetPanel:updateLinkedProperty");
}
return;
}
}
}
} }
final class PropertyCellRenderer extends DefaultTableCellRenderer { final class PropertyCellRenderer extends DefaultTableCellRenderer {