MASON-INTEREST-L Archives

June 2005

MASON-INTEREST-L@LISTSERV.GMU.EDU

Options: Use Monospaced Font
Show Text Part by Default
Show All Mail Headers

Message: [<< First] [< Prev] [Next >] [Last >>]
Topic: [<< First] [< Prev] [Next >] [Last >>]
Author: [<< First] [< Prev] [Next >] [Last >>]

Print Reply
Subject:
From:
Sean Luke <[log in to unmask]>
Reply To:
MASON Multiagent Simulation Toolkit <[log in to unmask]>
Date:
Sat, 18 Jun 2005 16:29:01 -0400
Content-Type:
multipart/mixed
Parts/Attachments:
text/plain (458 bytes) , Properties.java (11 kB) , text/plain (11 kB) , PropertyField.java (15 kB) , text/plain (15 kB)
Fixed.

There are two bugs actually:

1. If you enter "1.0" (or any x.0) in a field whose value is supposed
to be an int/long/etc., you get an error. Instead it's best that such
integer values be converted to ints (i.e., 1.0 -> 1).

2. If you turn the slider for integer values, it sends non-integral
double values (1.23 etc.).

I've updated our internal code. Here are the two files I modified to
fix things. Let me know if these work for you.

sim/util/Properties.java


package sim.util;
import java.util.*;

/**
   The abstract superclass of Property inspectors. Such a beast inspects an object and returns a list of variables for which there are get and possibly set methods.

   <P>There are two such inspectors: SimpleProperties (inspects objects for their slots) and CollectionProperties (inspects Collections, Maps, and arrays for their contents). The easiest way to get an appropriate Properties subclass instance is to simply call the static method <b>Properties.getProperties(<i>object to inspect</i>, .... )</b>. See the SimpleProperties and CollectionProperties classes for specifics about how they define what a Property is. SimpleProperties in particular will beinteresting..

   <p>Property inspectors enumerate the Properties in their provided object. SimpleProperties will enumerate each of the slots; and CollectionProperties will enumerate the elements in the corresponding array, Map, Collection, etc. You get the number of Properties with numProperties(). Properties have the following features:

   <ul>
   <li><b>Property Name</b> (Readable). A string, usually the name of the instance variable in the object, a stringified number ("1", "203", etc.) for Collections or Arrays, or a stringified version of the key in a Map.
   <li><b>Property Value</b> (Readable and possibly Writable). An object representing the value of the property. Numbers etc. are wrapped in their corresponding wrapper classes.
   <li><b>Writability</b> (Readable).
   <li><b>Type</b> (Readable). The type of the object as a Class. ints return Integer.TYPE, etc.
   <li><b>Composite Nature</b> (Readable). Whether or not the Property is composite or atomic. Atomic types are int, float, etc., plus String.
   <li><b>Domain</b> (Readable). Whether or not the object has defined a Domain (a set or range of legal values) for the Property. Domains allow a GUI to set up sliders, pull-down menus and combo-boxes, etc.
   </ul>
*/

public abstract class Properties implements java.io.Serializable
    {
    /** Returns a Properties object for the given object.
        If expandCollections is true, then if object is a Map, Indexed, or Collection,
        then it will be treated using CollectionProperties. Otherwise it will be
        treated using SimpleProperties. Arrays are always treated using CollectionProperties.
        If includeSuperclasses is true, then any SimpleProperties will include superclasses.
        If includeGetClass is true, then the Class property will be included.
        No finite domains will be produced (that is, getDomain(index) will return null for
        all properties).
    */
    public static Properties getProperties(Object object, boolean expandCollections, boolean includeSuperclasses, boolean includeGetClass)
        {
        return getProperties(object,expandCollections,includeSuperclasses,includeGetClass,true);
        }

    /** Returns a Properties object for the given object.
        If expandCollections is true, then if object is a Map, Indexed, or Collection,
        then it will be treated using CollectionProperties. Otherwise it will be
        treated using SimpleProperties. Arrays are always treated using CollectionProperties.
        If includeSuperclasses is true, then any SimpleProperties will include superclasses.
        If includeGetClass is true, then the Class property will be included.
        If includeDomains is true (which requires a CollectionProperties), then domains
        will be produced for properties according to the rules in the comments in getDomain(index)
        below. Otherwise all objects will return a null (infinite) domain.
    */
    public static Properties getProperties(Object object, boolean expandCollections, boolean includeSuperclasses, boolean includeGetClass, boolean includeDomains)
        {
        if (object == null) return new SimpleProperties(object, includeSuperclasses, includeGetClass);
        Class c = object.getClass();
        if (c.isArray()) return new CollectionProperties(object);
        else if (expandCollections && (Collection.class.isAssignableFrom(c) ||
                                       Indexed.class.isAssignableFrom(c) ||
                                       Map.class.isAssignableFrom(c)))
            return new CollectionProperties(object);
        else return new SimpleProperties(object, includeSuperclasses, includeGetClass, includeDomains);
        }


    /** Returns true if the number or order of properties could change at any time */
    public abstract boolean isVolatile();

    /** Returns the number of properties discovered in the object. */
    public abstract int numProperties();

    /** Returns the value of the property at the given index. */
    public abstract Object getValue(int index);

    /** Returns the domain of the property at the given index.
        Domains are defined by methods of the form <tt>public Object dom<i>Property</i>()</tt>
        and should generally take one of three forms:
        <dl>
        <dt><tt>null</tt>
        <dd>no domain (domain is infinite).
        <dt>An array of elements
        <dd>the domain consists solely of those elements.
        <dt>A <tt>sim.util.Interval</tt>
        <dd>the domain is an inclusive (closed) numerical range defined by the Interval.
        If the Interval returns Longs, then the domain is considered to be integral; else
        it is considered to be real-valued.
        </dl>
    */
    public Object getDomain(int index) { return null; }

    /** Returns true if the property at the given index is both readable and writable (as opposed to read-only). */
    public abstract boolean isReadWrite(int index);

    /** Returns true if the property at the given index is a "Composite" object, meaning it's not a primitive type (double, int, etc.) nor a String. */
    public boolean isComposite(int index)
        {
        if (index < 0 || index > numProperties()) return false;
        Class type = getTypeConversion(getType(index));
        return !(type.isPrimitive() || type == String.class);
        }

    /** Returns the name of the property at the given index. */
    public abstract String getName(int index);

    /** Returns the Class (or for primitive objects, the primitive TYPE) of the property at the given index. */
    public abstract Class getType(int index);

    abstract Object _setValue(int index, Object value);

    /** Sets the current value of the property. Simple values (byte, int, etc.)
        must be boxed (into Byte, Integer, etc.). Then returns the current (hopefully changed) value of the property.
        Returns null if an error occurs or if the index is out of the range [0 ... numProperties() - 1 ]*/
    public Object setValue(int index, Object value)
        {
        // so we can also have a setValue (int, String) which calls US
        return _setValue(index, value);
        }

    /** Sets the current value of the property to the value parsed from the given string.
        Then returns the current (hopefully changed) value of the property.
        Returns null if an error occurs or if the index is out of the range [0 ... numProperties() - 1 ]*/
    public Object setValue(int index, String value)
        {
        try
            {
            Class type = getType(index);
            if ( type == Boolean.TYPE ) return _setValue(index,Boolean.valueOf(value));
            else if ( type == Byte.TYPE )
                {
                try { return _setValue(index,Byte.valueOf(value)); }
                catch (NumberFormatException e) // try again for x.0 stuff
                    {
                    double d = Double.parseDouble(value);
                    byte b = (byte) d;
                    if (b==d) return _setValue(index,new Byte(b));
                    else throw e;
                    }
                }
            else if ( type == Short.TYPE )
                {
                try { return _setValue(index,Short.valueOf(value)); }
                catch (NumberFormatException e) // try again for x.0 stuff
                    {
                    double d = Double.parseDouble(value);
                    short b = (short) d;
                    if (b==d) return _setValue(index,new Short(b));
                    else throw e;
                    }
                }
            else if ( type == Integer.TYPE )
                {
                try { return _setValue(index,Integer.valueOf(value)); }
                catch (NumberFormatException e) // try again for x.0 stuff
                    {
                    double d = Double.parseDouble(value);
                    int b = (int) d;
                    if (b==d) return _setValue(index,new Integer(b));
                    else throw e;
                    }
                }
            else if ( type == Long.TYPE )
                {
                try { return _setValue(index,Long.valueOf(value)); }
                catch (NumberFormatException e) // try again for x.0 stuff
                    {
                    double d = Double.parseDouble(value);
                    long b = (long) d;
                    if (b==d) return _setValue(index,new Long(b));
                    else throw e;
                    }
                }
            else if ( type == Float.TYPE ) return _setValue(index,Float.valueOf(value));
            else if ( type == Double.TYPE ) return _setValue(index,Double.valueOf(value));
            else if ( type == Character.TYPE ) return _setValue(index,new Character(value.charAt(0)));
            else if ( type == String.class ) return _setValue(index,value);
            else return null;
            }
        catch (Exception e)
            {
            e.printStackTrace();
            return null;
            }
        }

    /*
    f = new JFrame();
    h = new sim.app.heatbugs.HeatBugsWithUI();
    c = new sim.display.Console(h);
    i = new sim.portrayal.SimpleInspector(new Foo(),h,"Yo, Mama!");
    f.getContentPane().setLayout(new BorderLayout());
    f.getContentPane().add(i,BorderLayout.CENTER);
    f.pack();
    f.show();
    */

    // converts boxed type classes into simple types
    protected Class getTypeConversion(Class type)
        {
        if (type==Boolean.class || type==Boolean.TYPE)
            return Boolean.TYPE;
        else if (type==Byte.class || type==Byte.TYPE)
            return Byte.TYPE;
        else if (type==Short.class || type==Short.TYPE)
            return Short.TYPE;
        else if (type==Integer.class || type==Integer.TYPE)
            return Integer.TYPE;
        else if (type==Long.class || type==Long.TYPE)
            return Long.TYPE;
        else if (type==Float.class || type==Float.TYPE)
            return Float.TYPE;
        else if (type==Double.class || type==Double.TYPE)
            return Double.TYPE;
        else if (type==Character.class || type==Character.TYPE)
            return Character.TYPE;
        else return type;
        }

    /** Call this to get a prettier print-name for an object -- converting arrays to a nicer format, for example. */
    public String betterToString(Object obj)
        {
        if (obj == null) return "null";
        Class c = obj.getClass();
        if (c.isArray()) return typeToName(c) + "@" + Integer.toHexString(obj.hashCode());
        else return "" + obj;
        }

    protected String typeToName( Class type )
        {
        if ( type == null ) return null;

        if ( type.isPrimitive() )
            {
            return type.toString();
            }
        else if ( type == String.class )
            {
            return "String";
            }
        else if ( type.isArray() )
            {
            Class componentType = type.getComponentType();

            Class convertedComponentType = getTypeConversion(componentType);
            return typeToName(convertedComponentType) + "[]";
            }
        else
            return null;
        }

    }




sim/util/gui/PropertyField.java


package sim.util.gui;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.awt.*;
import java.util.*;
import sim.util.*;

/**
   A simple class designed to allow the user to modify a property in the form of a string, number, boolean value, or option. PropertyField lets you control the values which the user sets by subclassing the class and overriding the newValue(val) method filters all newly user-set values and "corrects" them. Programmatically set values (by calling setValue(...)) are not filtered through newValue by default. If you need to filter, you should do setValue(newValue(val));

   <p>You can optionally specify how the string will be presented to the user: as a text field, as a text field with
   a slider (requiring certain numerical constraints on the text field), as a list of options (also requiring certain numerical constraints), as a check box (requiring the string to hold boolean values ("true" or "false"), or as a
   read-only field with a button to press (which in turn calls the viewProperty() method, which you may override).

   <p>The specifics about how to present the user with these options is described in the constructor documentation and in the documentation for setValues(...).

   <p>PropertyFields can also be set to be either read-only or read/write by the user. When the user edits a
   read/write PropertyField, the text field changes color. If the user then presses RETURN, the result is submitted
   to newValue(...). If the user presses ESCAPE, the result is cancelled and reset.
*/

public class PropertyField extends JComponent
    {
    public JComboBox list = new JComboBox();
    public JTextField valField = new JTextField();
    public JCheckBox checkField = new JCheckBox();
    public JButton viewButton = new JButton("View"); // optionally displayed instead of valField (array or Object)
    public JLabel viewLabel = new JLabel();
    public JLabel optionalLabel = new JLabel();
    static final int SLIDER_MAX = 800000;
    static final int SLIDER_WIDTH = 80;
    public JSlider slider = new JSlider(0,SLIDER_MAX)
        {
        public Dimension getMaximumSize() { return new Dimension(SLIDER_WIDTH, super.getMaximumSize().height); }
        public Dimension getPreferredSize() { return getMaximumSize(); }
        };

    public Border valFieldBorder;
    public Border emptyBorder;
    public String currentValue;
    public boolean isReadWrite;
    public Object domain;

    public int displayState;
    public static final int SHOW_CHECKBOX = 0;
    public static final int SHOW_TEXTFIELD = 1;
    public static final int SHOW_VIEWBUTTON = 2;
    public static final int SHOW_SLIDER = 3;
    public static final int SHOW_LIST = 4;

    public Color defaultColor;
    public Color editedColor = new Color(225,225,255);
    public void setEditedColor(Color c) { editedColor = c; }
    public Color getEditedColor() { return editedColor; }

    public void submit()
        {
        if (edited) { setValue(newValue( valField.getText() )); }
        }

    public void update()
        {
        setValue(getValue());
        }

    boolean edited = false;
    void setEdited(boolean edited)
        {
        this.edited = edited;
        if (edited)
            {
            valField.setBackground(editedColor);
            }
        else
            {
            valField.setBackground(isReadWrite ? defaultColor : checkField.getBackground());
            }
        }

    public KeyListener listener = new KeyListener()
        {
        public void keyReleased(KeyEvent keyEvent) { }
        public void keyTyped(KeyEvent keyEvent) { }
        public void keyPressed(KeyEvent keyEvent)
            {
            if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER)
                {
                submit();
                }
            else if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) // reset
                {
                update();
                }
            else
                {
                setEdited(true);
                }
            }
        };


      public ChangeListener checkListener = new ChangeListener()
        {
        public void stateChanged (ChangeEvent e)
            {
            setValue(newValue( new Boolean(checkField.isSelected()).toString() ));
            }
      };

    public ActionListener viewButtonListener = new ActionListener()
        {
        public void actionPerformed ( ActionEvent e )
            {
            viewProperty();
            }
        };

    public FocusAdapter focusAdapter = new FocusAdapter()
        {
        public void focusLost ( FocusEvent e )
            {
            submit();
            }
        };


    boolean sliding = false;

    public ChangeListener sliderListener = new ChangeListener()
        {
        public void stateChanged (ChangeEvent e)
            {
            if (domain != null && domain instanceof Interval)
                {
                double d = 0;
                Interval domain = (Interval)(PropertyField.this.domain);
                int i = slider.getValue();
                if (domain.isDouble())
                    {
                    double min = domain.getMin().doubleValue();
                    double max = domain.getMax().doubleValue();
                    d = (i / (double)SLIDER_MAX) * (max - min) + min;
                    }
                else // long
                    {
                    long min = domain.getMin().longValue();
                    long max = domain.getMax().longValue();
                    d = (double)((long)((i / (double)SLIDER_MAX) * (max - min) + min)); // floor to an integer value
                    }
                sliding = true;
                setValue(newValue("" + d));
                sliding = false;
                }
            }
        };

    public ActionListener listListener = new ActionListener()
        {
        public void actionPerformed ( ActionEvent e )
            {
            if (!settingList)
                setValue(newValue(""+list.getSelectedIndex()));
            }
        };

    boolean settingList = false;

    /** Sets the value, not filtering it through newValue(val) first. */
    public void setValue(String val)
        {
        switch(displayState)
            {
            case SHOW_SLIDER:
                setEdited(false);
                if (!sliding) { slide(val); }
                valField.setText(val);
                break;
            case SHOW_TEXTFIELD:
                setEdited(false);
                valField.setText(val);
                break;
            case SHOW_CHECKBOX:
                if(val!=null && val.equals("true"))
                    checkField.setSelected(true);
                else
                    checkField.setSelected(false);
                break;
            case SHOW_VIEWBUTTON:
                viewLabel.setText(val);
                break;
            case SHOW_LIST:
                settingList = true;
                try { list.setSelectedIndex(Integer.parseInt(val)); }
                catch (Exception e) { settingList = false; throw new RuntimeException(""+e); }
                settingList = false;
                break;
            default:
                break;
            }
        currentValue = val;
        }

    void slide(String val)
        {
        try
            {
            if (domain instanceof Interval)
                {
                Interval domain = (Interval)(this.domain);
                double d = Double.parseDouble(val);
                double min = domain.getMin().doubleValue();
                double max = domain.getMax().doubleValue();
                int i = (int)((d - min) / (max - min) * SLIDER_MAX);
                slider.setValue(i);
                }
            }
        catch (Exception e) { }
        }

    /** Returns the most recently set value. */
    public String getValue()
        {
        return currentValue;
        }

    /** Constructs a PropertyField as just a writeable, empty text field. */
    public PropertyField()
        {
        this(null,"",true);
        }

    /** Constructs a PropertyField as a writeable text field with the provided initial value. */
    public PropertyField(String initialValue)
        {
        this(null,initialValue,true);
        }

    /** Constructs a PropertyField as a text field with the provided initial value, either writeable or not. */
    public PropertyField(String initialValue, boolean isReadWrite)
        {
        this(null,initialValue,isReadWrite);
        }

    /** Constructs a labelled PropertyField as a writeable text field with the provided initial value. */
    public PropertyField(String label, String initialValue)
        {
        this(label,initialValue,true);
        }

    /** Constructs a labelled PropertyField as a text field with the provided initial value, either writeable or not. */
    public PropertyField(String label, String initialValue, boolean isReadWrite)
        {
        this(label,initialValue,isReadWrite, null, SHOW_TEXTFIELD);
        }

    /** Constructs a PropertyField with an optional label, an initial value, a "writeable" flag, an optional domain
        (for the slider and list options), and a display form (checkboxes, view buttons, text fields, sliders, or lists).
        <ul>
        <li>If show is SHOW_CHECKBOX, a checkbox will be shown (expecting "true" and "false" string values); pass in null for domain.
        <li>If show is SHOW_VIEWBUTTON, a view button will be shown (expecting a true object); pass in null for domain.
        <li>If show is SHOW_TEXTFIELD, a textfield will be shown; pass in null for domain.
        <li>If show is SHOW_SLIDER, both a textfield and a slider will be shown; the initialValue must be a number, and
        domain must be a sim.util.Interval.
        In this case, newValue(...) will be passed a String holding a number in the Interval range and must return
        a number. PropertyField will automatically make certain that the numbers are integral or real-valued; you
        do not need to check this so long as the Interval returns Longs or Doubles respectively. If isReadWrite is false,
        then the slider is not shown -- only the textfield.
        <li>If show is SHOW_LIST, a list will be shown; the initialValue must be an integer specifying the number in the list, and domain must be an array of Objects (strings, whatnot) or a java.util.List providing the objects in the list.
        In this case, newValue(...) will be passed a String holding a number; that number is the index in the list
        which the user has checked. newValue(...) must also return a String with the desired index for the list to be
        set to. */
    public PropertyField(String label, String initialValue, boolean isReadWrite, Object domain, int show)
        {
        // create object
        setLayout(new BorderLayout());
        add(optionalLabel,BorderLayout.WEST);

        valFieldBorder = valField.getBorder();
        Insets i = valFieldBorder.getBorderInsets(valField);
        emptyBorder = new EmptyBorder(i.top,i.left,i.bottom,i.right);

        defaultColor = valField.getBackground();
        valField.addKeyListener(listener);
        valField.addFocusListener(focusAdapter);
        viewButton.addActionListener(viewButtonListener);
        slider.addChangeListener(sliderListener);
        list.addActionListener(listListener);


        // set values
        setValues(label, initialValue, isReadWrite, domain, show);
        }

    /* Resets a PropertyField with an optional label, an initial value, a "writeable" flag, an optional domain
       (for the slider and list options), and a display form (checkboxes, view buttons, text fields, sliders, or lists).
       <ul>
       <li>If show is SHOW_CHECKBOX, a checkbox will be shown (expecting "true" and "false" string values); pass in null for domain.
       <li>If show is SHOW_VIEWBUTTON, a view button will be shown (expecting a true object); pass in null for domain.
       <li>If show is SHOW_TEXTFIELD, a textfield will be shown; pass in null for domain.
       <li>If show is SHOW_SLIDER, both a textfield and a slider will be shown; the initialValue must be a number, and
       domain must be a sim.util.Interval.
       In this case, newValue(...) will be passed a String holding a number in the Interval range and must return
       a number. PropertyField will automatically make certain that the numbers are integral or real-valued; you
       do not need to check this so long as the Interval returns Longs or Doubles respectively. If isReadWrite is false,
       then the slider is not shown -- only the textfield.
       <li>If show is SHOW_LIST, a list will be shown; the initialValue must be an integer specifying the number in the list, and domain must be an array of Objects (strings, whatnot) or a java.util.List providing the objects in the list.
       In this case, newValue(...) will be passed a String holding a number; that number is the index in the list
       which the user has checked. newValue(...) must also return a String with the desired index for the list to be
       set to.
    */
    public void setValues(String label, String initialValue, boolean isReadWrite, Object domain, int show)
        {
        this.domain = domain;
        removeAll();
        add(optionalLabel,BorderLayout.WEST);

        // some conversions
        if (show==SHOW_SLIDER && !isReadWrite) show = SHOW_TEXTFIELD;
        if (domain !=null && domain.getClass().isArray())
            {
            domain = Arrays.asList((Object[])domain);
            }

        displayState = show;
        switch(displayState)
            {
            case SHOW_SLIDER:
                JPanel p = new JPanel();
                p.setLayout(new BorderLayout());
                p.add(valField, BorderLayout.CENTER);
                if (isReadWrite && domain!=null && domain instanceof Interval)
                    p.add(slider, BorderLayout.WEST);
                add(p,BorderLayout.CENTER);
                break;
            case SHOW_TEXTFIELD:
                add(valField, BorderLayout.CENTER);
                break;
            case SHOW_CHECKBOX:
                add(checkField, BorderLayout.CENTER);
                break;
            case SHOW_VIEWBUTTON:
                add(viewButton, BorderLayout.EAST);
                add(viewLabel, BorderLayout.CENTER);
                break;
            case SHOW_LIST:
                if (domain != null && domain instanceof java.util.List)
                    {
                    settingList = true;
                    list.setEditable(false);
                    list.setModel(new DefaultComboBoxModel(new Vector((java.util.List)domain)));
                    add(list,BorderLayout.CENTER);
                    list.setEnabled(isReadWrite);
                    settingList = false;
                    }
                break;
            default:
                break;
            }
        revalidate();
        repaint();

        currentValue = initialValue;
        optionalLabel.setText(label);

        checkField.setEnabled(isReadWrite);
        valField.setEditable(isReadWrite);
        valField.setBorder(isReadWrite? valFieldBorder : emptyBorder);

        this.isReadWrite = isReadWrite;
        setValue(currentValue);
        }

    /** Override this to be informed when a new value has been set.
        The return value should be the value you want the display to show
        instead. */
    public String newValue(String newValue)
        {
        return newValue;
        }

    /** Override this to be informed when a property is to be viewed in its
        own inspector because the user pressed the "view" button. */
    public void viewProperty()
        {
        }

    public void setToolTipText(String text)
        {
        super.setToolTipText(text);
        valField.setToolTipText(text);
        checkField.setToolTipText(text);
        optionalLabel.setToolTipText(text);
        viewButton.setToolTipText(text);
        viewLabel.setToolTipText(text);
        slider.setToolTipText(text);
        list.setToolTipText(text);
        }

    public Dimension getMinimumSize()
        {
        Dimension s = super.getMinimumSize();
        s.height = valField.getMinimumSize().height;
        return s;
        }
    public Dimension getPreferredSize()
        {
        Dimension s = super.getPreferredSize();
        s.height = valField.getPreferredSize().height;
        return s;
        }
    }




Sean

ATOM RSS1 RSS2