I've broken out how the FieldEditors access their data into a separate Accessor plugin. This way, we can replace the default DObjectAccessor with (for example) an accessor that deals with multiple DObjects simultaneously.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6030 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Mike Thomas
2010-02-04 22:22:49 +00:00
parent c326538c07
commit e206d26515
3 changed files with 259 additions and 102 deletions
@@ -40,7 +40,6 @@ import com.samskivert.util.ComparableArrayList;
import com.samskivert.swing.ObjectEditorTable; import com.samskivert.swing.ObjectEditorTable;
import com.samskivert.swing.event.CommandEvent; import com.samskivert.swing.event.CommandEvent;
import com.threerings.presents.dobj.AttributeChangeListener; import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent; import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObject;
@@ -54,8 +53,16 @@ import com.threerings.presents.dobj.SetListener;
* Allows simple editing of DSets within a distributed object. * Allows simple editing of DSets within a distributed object.
*/ */
public class DSetEditor<E extends DSet.Entry> extends JPanel public class DSetEditor<E extends DSet.Entry> extends JPanel
implements AttributeChangeListener, SetListener<E>, ActionListener implements ActionListener
{ {
public interface Accessor<F extends DSet.Entry>
{
void added ();
void removed ();
void updateEntry (String setName, DSet.Entry entry);
ObjectEditorTable.FieldInterpreter getInterp (ObjectEditorTable.FieldInterpreter interp);
}
/** /**
* Construct a DSet editor to merely display the specified set. * Construct a DSet editor to merely display the specified set.
* *
@@ -115,13 +122,24 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
{ {
super(new BorderLayout()); super(new BorderLayout());
_setter = setter;
_setName = setName; _setName = setName;
_set = _setter.getSet(setName);
_entryFilter = entryFilter; _entryFilter = entryFilter;
_table = new ObjectEditorTable(entryClass, editableFields, interp, displayFields); _entryClass = entryClass;
_editableFields = editableFields;
_interp = interp;
_displayFields = displayFields;
setAccessor(new DObjectAccessor<E>(setter, setName));
}
public void setAccessor (Accessor accessor)
{
removeAll();
_accessor = accessor;
_table = new ObjectEditorTable(_entryClass, _editableFields, _accessor.getInterp(_interp),
_displayFields);
add(new JScrollPane(_table), BorderLayout.CENTER); add(new JScrollPane(_table), BorderLayout.CENTER);
} }
@@ -153,17 +171,14 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
public void addNotify () public void addNotify ()
{ {
super.addNotify(); super.addNotify();
_setter.addListener(this); _accessor.added();
_table.addActionListener(this); _table.addActionListener(this);
// populate the table
refreshData();
} }
@Override @Override
public void removeNotify () public void removeNotify ()
{ {
_setter.removeListener(this); _accessor.removed();
_table.removeActionListener(this); _table.removeActionListener(this);
super.removeNotify(); super.removeNotify();
} }
@@ -191,85 +206,27 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
} }
} }
// documentation inherited from interface SetListener
public void entryAdded (EntryAddedEvent<E> event)
{
if (event.getName().equals(_setName)) {
addEntry(event.getEntry());
}
}
// documentation inherited from interface SetListener
public void entryRemoved (EntryRemovedEvent<E> event)
{
if (event.getName().equals(_setName)) {
removeKey(event.getKey());
}
}
// documentation inherited from interface SetListener
public void entryUpdated (EntryUpdatedEvent<E> event)
{
if (event.getName().equals(_setName)) {
E entry = event.getEntry();
int index = _keys.indexOf(entry.getKey());
if (index != -1) {
// We have it, so either update or remove
if (_entryFilter == null || _entryFilter.apply(entry)) {
_table.updateDatum(entry, index);
} else {
removeKey(entry.getKey());
}
} else {
// We DON'T have it, so try to add it in case we care about it
addEntry(entry);
}
}
}
// documentation inherited from interface SetListener
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(_setName)) {
// the whole set changed so we need to refetch it from the object
_set = _setter.getSet(_setName);
refreshData();
}
}
// documentation inherited from interface ActionListener // documentation inherited from interface ActionListener
public void actionPerformed (ActionEvent event) public void actionPerformed (ActionEvent event)
{ {
CommandEvent ce = (CommandEvent)event; CommandEvent ce = (CommandEvent)event;
_setter.updateSet(_setName, (DSet.Entry)ce.getArgument()); _accessor.updateEntry(_setName, (DSet.Entry)ce.getArgument());
} }
protected void refreshData () public void setData (ComparableArrayList<Comparable<Object>> keys, Object[] data)
{ {
_keys = new ComparableArrayList<Comparable<Object>>(); _keys = keys;
E[] entries; _table.setData(data);
}
if (_entryFilter == null) { public Predicate<E> getFilter ()
@SuppressWarnings("unchecked") E[] tmp = (E[])new DSet.Entry[_set.size()]; {
entries = tmp; return _entryFilter;
_set.toArray(entries); }
} else { public String getSetName ()
// Do some shuffling to get out a filtered array. {
Iterator<E> itr = Iterators.filter(_set.iterator(), _entryFilter); return _setName;
ArrayList<E> list = Lists.newArrayList();
Iterators.addAll(list, itr);
@SuppressWarnings("unchecked") E[] tmp = (E[])new DSet.Entry[list.size()];
entries = tmp;
list.toArray(entries);
}
for (E entry : entries) {
_keys.insertSorted(getKey(entry));
}
_table.setData(entries); // this works because DSet itself is sorted
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -278,8 +235,135 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
return (Comparable<Object>)entry.getKey(); return (Comparable<Object>)entry.getKey();
} }
/** The object that contains the set we're displaying. */ protected class DObjectAccessor<F extends E>
protected DObject _setter; implements AttributeChangeListener, SetListener<F>, Accessor<F>
{
public DObjectAccessor (DObject obj, String setName)
{
_obj = obj;
_setName = setName;
}
public ObjectEditorTable.FieldInterpreter getInterp (
ObjectEditorTable.FieldInterpreter interp)
{
return interp;
}
public void added ()
{
_obj.addListener(this);
refreshSet();
refreshData();
}
public void removed ()
{
_obj.removeListener(this);
}
public void refreshSet ()
{
_set = _obj.getSet(_setName);
}
public void updateEntry (String setName, DSet.Entry entry)
{
_obj.updateSet(setName, entry);
}
// documentation inherited from interface SetListener
public void entryAdded (EntryAddedEvent<F> event)
{
if (event.getName().equals(_setName)) {
addEntry(event.getEntry());
}
}
// documentation inherited from interface SetListener
public void entryRemoved (EntryRemovedEvent<F> event)
{
if (event.getName().equals(_setName)) {
removeKey(event.getKey());
}
}
protected void refreshData ()
{
ComparableArrayList<Comparable<Object>> keys =
new ComparableArrayList<Comparable<Object>>();
E[] entries;
if (_entryFilter == null) {
entries = createArray();
} else {
// Do some shuffling to get out a filtered array.
Iterator<F> itr = Iterators.filter(iterator(), _entryFilter);
ArrayList<F> list = Lists.newArrayList();
Iterators.addAll(list, itr);
@SuppressWarnings("unchecked") F[] tmp = (F[])new DSet.Entry[list.size()];
entries = tmp;
list.toArray(entries);
}
for (E entry : entries) {
keys.insertSorted(getKey(entry));
}
setData(keys, entries); // this works because DSet itself is sorted
}
// documentation inherited from interface SetListener
public void entryUpdated (EntryUpdatedEvent<F> event)
{
if (event.getName().equals(_setName)) {
E entry = event.getEntry();
int index = _keys.indexOf(entry.getKey());
if (index != -1) {
// We have it, so either update or remove
if (_entryFilter == null || _entryFilter.apply(entry)) {
_table.updateDatum(entry, index);
} else {
removeKey(entry.getKey());
}
} else {
// We DON'T have it, so try to add it in case we care about it
addEntry(entry);
}
}
}
// documentation inherited from interface SetListener
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(_setName)) {
// the whole set changed so we need to refetch it from the object
refreshSet();
refreshData();
}
}
public E[] createArray ()
{
@SuppressWarnings("unchecked") F[] tmp = (F[])new DSet.Entry[_set.size()];
F[] entries = tmp;
_set.toArray(entries);
return entries;
}
public Iterator<F> iterator ()
{
return _set.iterator();
}
protected DObject _obj;
protected DSet<F> _set;
protected String _setName;
}
/** The name of the set in that object. */ /** The name of the set in that object. */
protected String _setName; protected String _setName;
@@ -287,15 +371,20 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
/** An optional predicate to decide whether actually care about displaying a given entry. */ /** An optional predicate to decide whether actually care about displaying a given entry. */
protected Predicate<E> _entryFilter; protected Predicate<E> _entryFilter;
/** The set itself. */ /** Provides access to our data we're editing. */
protected DSet<E> _set; protected Accessor<E> _accessor;
/** An array we use to track our entries' positions by key. */
protected ComparableArrayList<Comparable<Object>> _keys;
/** The table used to edit. */ /** The table used to edit. */
protected ObjectEditorTable _table; protected ObjectEditorTable _table;
/** An array we use to track our entries' positions by key. */
protected ComparableArrayList<Comparable<Object>> _keys;
protected Class<?> _entryClass;
protected String[] _editableFields;
protected ObjectEditorTable.FieldInterpreter _interp;
protected String[] _displayFields;
/** The minimum height for our editor UI. */ /** The minimum height for our editor UI. */
protected static final int MIN_HEIGHT = 200; protected static final int MIN_HEIGHT = 200;
} }
@@ -51,11 +51,20 @@ import static com.threerings.admin.Log.log;
public abstract class FieldEditor extends JPanel public abstract class FieldEditor extends JPanel
implements AttributeChangeListener, ActionListener, FocusListener implements AttributeChangeListener, ActionListener, FocusListener
{ {
/** The interface defining how the editor interacts with its data. */
public interface Accessor
{
void added ();
void removed ();
void set (Field field, Object value);
Object get (Field field);
}
public FieldEditor (PresentsContext ctx, Field field, DObject object) public FieldEditor (PresentsContext ctx, Field field, DObject object)
{ {
_ctx = ctx; _ctx = ctx;
_field = field; _field = field;
_object = object; setAccessor(new DObjectAccessor(object));
// create our interface elements // create our interface elements
setLayout(new HGroupLayout(HGroupLayout.STRETCH)); setLayout(new HGroupLayout(HGroupLayout.STRETCH));
@@ -67,13 +76,18 @@ public abstract class FieldEditor extends JPanel
updateBorder(false); updateBorder(false);
} }
public void setAccessor (Accessor accessor)
{
_accessor = accessor;
}
@Override @Override
public void addNotify () public void addNotify ()
{ {
super.addNotify(); super.addNotify();
// listen to the object while we're visible // listen to the object while we're visible
_object.addListener(FieldEditor.this); _accessor.added();
displayValue(getValue()); displayValue(getValue());
} }
@@ -83,11 +97,17 @@ public abstract class FieldEditor extends JPanel
super.removeNotify(); super.removeNotify();
// stop listening when we're hidden // stop listening when we're hidden
_object.removeListener(FieldEditor.this); _accessor.removed();
} }
// documentation inherited from interface // documentation inherited from interface
public void attributeChanged (AttributeChangedEvent event) public void attributeChanged (AttributeChangedEvent event)
{
noteUpdatedExternally();
}
/** Update ourselves to reflect a change from outside the editor. */
public void noteUpdatedExternally ()
{ {
displayValue(getValue()); displayValue(getValue());
updateBorder(false); updateBorder(false);
@@ -105,11 +125,7 @@ public abstract class FieldEditor extends JPanel
// submit an attribute changed event with the new value // submit an attribute changed event with the new value
if (!ObjectUtil.equals(value, getValue())) { if (!ObjectUtil.equals(value, getValue())) {
try { _accessor.set(_field, value);
_object.changeAttribute(_field.getName(), value);
} catch (ObjectAccessException oae) {
log.warning("Failed to update field " + _field.getName() + ": " + oae);
}
} }
} }
@@ -151,12 +167,7 @@ public abstract class FieldEditor extends JPanel
*/ */
protected Object getValue () protected Object getValue ()
{ {
try { return _accessor.get(_field);
return _field.get(_object);
} catch (Exception e) {
log.warning("Failed to fetch field", "field", _field, "object", _object, "error", e);
return null;
}
} }
/** /**
@@ -172,8 +183,51 @@ public abstract class FieldEditor extends JPanel
} }
} }
/**
* A simple accessor that knows how to interact with a DObject - this is normally what is used.
*/
protected class DObjectAccessor
implements Accessor
{
public DObjectAccessor (DObject obj)
{
_obj = obj;
}
public void added ()
{
_obj.addListener(FieldEditor.this);
}
public void removed ()
{
_obj.removeListener(FieldEditor.this);
}
public void set (Field field, Object value)
{
try {
_obj.changeAttribute(field.getName(), value);
} catch (ObjectAccessException oae) {
log.warning("Failed to update field " + field.getName() + ": " + oae);
}
}
public Object get (Field field)
{
try {
return field.get(_obj);
} catch (Exception e) {
log.warning("Failed to fetch field", "field", field, "object", _obj, "error", e);
return null;
}
}
protected DObject _obj;
}
protected PresentsContext _ctx; protected PresentsContext _ctx;
protected Field _field; protected Field _field;
protected DObject _object; protected Accessor _accessor;
protected JLabel _label; protected JLabel _label;
} }
@@ -56,6 +56,11 @@ import com.threerings.presents.dobj.SetListener;
public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
implements AttributeChangeListener, SetListener<E> implements AttributeChangeListener, SetListener<E>
{ {
public interface AccessorFactory
{
public DSetEditor.Accessor createAccessor (DSetEditor editor);
}
/** /**
* Used to divide various entires into different groups. * Used to divide various entires into different groups.
*/ */
@@ -170,6 +175,11 @@ public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
add(_tabs); add(_tabs);
} }
public void setAccessorFactory (AccessorFactory accessorFactory)
{
_accessorFactory = accessorFactory;
}
protected void computeTabs () protected void computeTabs ()
{ {
_grouper.computeGroups(_setter.<E>getSet(_setName)); _grouper.computeGroups(_setter.<E>getSet(_setName));
@@ -179,6 +189,9 @@ public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
if (!_editors.containsKey(group)) { if (!_editors.containsKey(group)) {
DSetEditor<E> editor = createEditor( DSetEditor<E> editor = createEditor(
_setter, _setName, _entryClass, _editableFields, _interp, _grouper, group); _setter, _setName, _entryClass, _editableFields, _interp, _grouper, group);
if (_accessorFactory != null) {
editor.setAccessor(_accessorFactory.createAccessor(editor));
}
_tabs.add(group, editor); _tabs.add(group, editor);
_editors.put(group, editor); _editors.put(group, editor);
} }
@@ -257,6 +270,7 @@ public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
protected final String[] _editableFields; protected final String[] _editableFields;
protected final ObjectEditorTable.FieldInterpreter _interp; protected final ObjectEditorTable.FieldInterpreter _interp;
protected final EntryGrouper<E> _grouper; protected final EntryGrouper<E> _grouper;
protected AccessorFactory _accessorFactory;
protected JTabbedPane _tabs; protected JTabbedPane _tabs;
protected HashMap<String, DSetEditor<E>> _editors = Maps.newHashMap(); protected HashMap<String, DSetEditor<E>> _editors = Maps.newHashMap();