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:
@@ -40,7 +40,6 @@ import com.samskivert.util.ComparableArrayList;
|
||||
|
||||
import com.samskivert.swing.ObjectEditorTable;
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
|
||||
import com.threerings.presents.dobj.AttributeChangeListener;
|
||||
import com.threerings.presents.dobj.AttributeChangedEvent;
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -115,13 +122,24 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
|
||||
{
|
||||
super(new BorderLayout());
|
||||
|
||||
_setter = setter;
|
||||
_setName = setName;
|
||||
_set = _setter.getSet(setName);
|
||||
_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);
|
||||
}
|
||||
|
||||
@@ -153,17 +171,14 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
_setter.addListener(this);
|
||||
_accessor.added();
|
||||
_table.addActionListener(this);
|
||||
|
||||
// populate the table
|
||||
refreshData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify ()
|
||||
{
|
||||
_setter.removeListener(this);
|
||||
_accessor.removed();
|
||||
_table.removeActionListener(this);
|
||||
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
|
||||
public void actionPerformed (ActionEvent 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>>();
|
||||
E[] entries;
|
||||
_keys = keys;
|
||||
_table.setData(data);
|
||||
}
|
||||
|
||||
if (_entryFilter == null) {
|
||||
@SuppressWarnings("unchecked") E[] tmp = (E[])new DSet.Entry[_set.size()];
|
||||
entries = tmp;
|
||||
_set.toArray(entries);
|
||||
public Predicate<E> getFilter ()
|
||||
{
|
||||
return _entryFilter;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Do some shuffling to get out a filtered array.
|
||||
Iterator<E> itr = Iterators.filter(_set.iterator(), _entryFilter);
|
||||
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
|
||||
public String getSetName ()
|
||||
{
|
||||
return _setName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -278,8 +235,135 @@ public class DSetEditor<E extends DSet.Entry> extends JPanel
|
||||
return (Comparable<Object>)entry.getKey();
|
||||
}
|
||||
|
||||
/** The object that contains the set we're displaying. */
|
||||
protected DObject _setter;
|
||||
protected class DObjectAccessor<F extends E>
|
||||
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. */
|
||||
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. */
|
||||
protected Predicate<E> _entryFilter;
|
||||
|
||||
/** The set itself. */
|
||||
protected DSet<E> _set;
|
||||
|
||||
/** An array we use to track our entries' positions by key. */
|
||||
protected ComparableArrayList<Comparable<Object>> _keys;
|
||||
/** Provides access to our data we're editing. */
|
||||
protected Accessor<E> _accessor;
|
||||
|
||||
/** The table used to edit. */
|
||||
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. */
|
||||
protected static final int MIN_HEIGHT = 200;
|
||||
}
|
||||
|
||||
@@ -51,11 +51,20 @@ import static com.threerings.admin.Log.log;
|
||||
public abstract class FieldEditor extends JPanel
|
||||
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)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_field = field;
|
||||
_object = object;
|
||||
setAccessor(new DObjectAccessor(object));
|
||||
|
||||
// create our interface elements
|
||||
setLayout(new HGroupLayout(HGroupLayout.STRETCH));
|
||||
@@ -67,13 +76,18 @@ public abstract class FieldEditor extends JPanel
|
||||
updateBorder(false);
|
||||
}
|
||||
|
||||
public void setAccessor (Accessor accessor)
|
||||
{
|
||||
_accessor = accessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
|
||||
// listen to the object while we're visible
|
||||
_object.addListener(FieldEditor.this);
|
||||
_accessor.added();
|
||||
displayValue(getValue());
|
||||
}
|
||||
|
||||
@@ -83,11 +97,17 @@ public abstract class FieldEditor extends JPanel
|
||||
super.removeNotify();
|
||||
|
||||
// stop listening when we're hidden
|
||||
_object.removeListener(FieldEditor.this);
|
||||
_accessor.removed();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
noteUpdatedExternally();
|
||||
}
|
||||
|
||||
/** Update ourselves to reflect a change from outside the editor. */
|
||||
public void noteUpdatedExternally ()
|
||||
{
|
||||
displayValue(getValue());
|
||||
updateBorder(false);
|
||||
@@ -105,11 +125,7 @@ public abstract class FieldEditor extends JPanel
|
||||
|
||||
// submit an attribute changed event with the new value
|
||||
if (!ObjectUtil.equals(value, getValue())) {
|
||||
try {
|
||||
_object.changeAttribute(_field.getName(), value);
|
||||
} catch (ObjectAccessException oae) {
|
||||
log.warning("Failed to update field " + _field.getName() + ": " + oae);
|
||||
}
|
||||
_accessor.set(_field, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,12 +167,7 @@ public abstract class FieldEditor extends JPanel
|
||||
*/
|
||||
protected Object getValue ()
|
||||
{
|
||||
try {
|
||||
return _field.get(_object);
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to fetch field", "field", _field, "object", _object, "error", e);
|
||||
return null;
|
||||
}
|
||||
return _accessor.get(_field);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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 Field _field;
|
||||
protected DObject _object;
|
||||
protected Accessor _accessor;
|
||||
protected JLabel _label;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ import com.threerings.presents.dobj.SetListener;
|
||||
public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
|
||||
implements AttributeChangeListener, SetListener<E>
|
||||
{
|
||||
public interface AccessorFactory
|
||||
{
|
||||
public DSetEditor.Accessor createAccessor (DSetEditor editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to divide various entires into different groups.
|
||||
*/
|
||||
@@ -170,6 +175,11 @@ public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
|
||||
add(_tabs);
|
||||
}
|
||||
|
||||
public void setAccessorFactory (AccessorFactory accessorFactory)
|
||||
{
|
||||
_accessorFactory = accessorFactory;
|
||||
}
|
||||
|
||||
protected void computeTabs ()
|
||||
{
|
||||
_grouper.computeGroups(_setter.<E>getSet(_setName));
|
||||
@@ -179,6 +189,9 @@ public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
|
||||
if (!_editors.containsKey(group)) {
|
||||
DSetEditor<E> editor = createEditor(
|
||||
_setter, _setName, _entryClass, _editableFields, _interp, _grouper, group);
|
||||
if (_accessorFactory != null) {
|
||||
editor.setAccessor(_accessorFactory.createAccessor(editor));
|
||||
}
|
||||
_tabs.add(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 ObjectEditorTable.FieldInterpreter _interp;
|
||||
protected final EntryGrouper<E> _grouper;
|
||||
protected AccessorFactory _accessorFactory;
|
||||
|
||||
protected JTabbedPane _tabs;
|
||||
protected HashMap<String, DSetEditor<E>> _editors = Maps.newHashMap();
|
||||
|
||||
Reference in New Issue
Block a user