ZenHarbinger / l2fprod-properties-editor

Fork and Extension of l2fprod's Java properties editing component.
http://tros.org/l2fprod-properties-editor/
Apache License 2.0
2 stars 4 forks source link

Adding a combobox and a button in one editor #1

Closed tonybeckett closed 8 years ago

tonybeckett commented 8 years ago

Attempting to create a combobox and button editor. The code runs but I can not get the combobox to appear unless I select the button.

Any thoughts? Cheers Tony

 public TBPropertyEditor()
    {
        combo = new JComboBox()
        {
            @Override
            public void setSelectedItem(Object anObject)
            {
                oldValue = getSelectedItem();
                super.setSelectedItem(anObject);
                selectedValue = anObject;
            }

        };
        editor = new JPanel(new PercentLayout(PercentLayout.HORIZONTAL, 0));
        ((JPanel) editor).add(combo);
        //       ((JPanel) editor).add("*", label = new Renderer());
        //       label.setOpaque(false);
        ((JPanel) editor).add(button = ComponentFactory.Helper.getFactory()
                .createMiniButton());
        button.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                editProperties(selectedValue);
            }
        });

        ((JPanel) editor).setOpaque(true);

        combo.setRenderer(new TBPropertyEditor.Renderer());
        combo.setEditable(false);
        combo.addPopupMenuListener(new PopupMenuListener()
        {
            @Override
            public void popupMenuCanceled(PopupMenuEvent e)
            {
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
            {
                TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
            }

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e)
            {

            }
        });
        combo.addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                if (e.getKeyCode() == KeyEvent.VK_ENTER)
                {
                    TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
                }
            }
        });
ZenHarbinger commented 8 years ago

Ok, I have to admit, I'm not sure what you are aiming for. Do you want a drop down/combo box w/ a selection of items? Can you post/gist your whole class?

tonybeckett commented 8 years ago

I want to select one of the items and then be able to then edit the properties of the selected item. The button launches another property editor for that selected item.

When you click on a Color entry the two buttons appear. When you click on this combobox nothing happens. It seems to be a mouse action is missing.


public class TBPropertyEditor extends AbstractPropertyEditor
{

    private final JButton button;
    //   private final Renderer label;
    private Object oldValue;
    private Object selectedValue;
    private Icon[] icons;
    private final Map<String, Object> map = new HashMap();
    private JComboBox combo;

    public TBPropertyEditor()
    {
        combo = new JComboBox()
        {
            @Override
            public void setSelectedItem(Object anObject)
            {
                oldValue = getSelectedItem();
                super.setSelectedItem(anObject);
                selectedValue = anObject;
            }

        };
        editor = new JPanel(new PercentLayout(PercentLayout.HORIZONTAL, 0));
        ((JPanel) editor).add(combo);
        //       ((JPanel) editor).add("*", label = new Renderer());
        //       label.setOpaque(false);
        ((JPanel) editor).add(button = ComponentFactory.Helper.getFactory()
                .createMiniButton());
        button.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                editProperties(selectedValue);
            }
        });

        ((JPanel) editor).setOpaque(true);

        combo.setRenderer(new TBPropertyEditor.Renderer());
        combo.setEditable(false);
        combo.addPopupMenuListener(new PopupMenuListener()
        {
            @Override
            public void popupMenuCanceled(PopupMenuEvent e)
            {
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
            {
                TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
            }

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e)
            {

            }
        });
        combo.addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                if (e.getKeyCode() == KeyEvent.VK_ENTER)
                {
                    TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
                }
            }
        });

        ArrayList<String> items = new ArrayList();

        List itemList = getItems();
        for (Object o : itemList)
        {
            items.add(o.getClass().getSimpleName());
            map.put(o.getClass().getSimpleName(), o);
        }
        setAvailableValues(items.toArray());

//        combo.setSelectedIndex(-1);
    }

    protected List getItems()
    {
        return new ArrayList();
    }

    public void setAvailableValues(Object[] values)
    {
        combo.setModel(new DefaultComboBoxModel(values));
    }

    public void setAvailableIcons(Icon[] icons)
    {
        this.icons = icons;
    }

    protected void editProperties(Object selectedItem)
    {

//        ResourceManager rm = ResourceManager.all(FilePropertyEditor.class);
//        String title = rm.getString("TBPropertyEditor.title");
//        Color selectedColor = JColorChooser.showDialog(editor, title, color);
//
//        if (selectedColor != null)
//        {
//            Color oldColor = color;
//            Color newColor = selectedColor;
////            label.setValue(newColor);
//            color = newColor;
//            firePropertyChange(oldColor, newColor);
//        }
    }

    @Override
    public Object getValue()
    {
        Object selected = combo.getSelectedItem();
        if (selected instanceof Value)
        {
            return ((Value) selected).value;
        } else
        {
            return map.get((String) selected);
        }
    }

    @Override
    public void setValue(Object value)
    {

        Object current = null;
        int index = -1;
        for (int i = 0, c = combo.getModel().getSize(); i < c; i++)
        {
            current = combo.getModel().getElementAt(i);
            if (value == current || (current != null && current.equals(value)))
            {
                index = i;
                break;
            }
        }
        combo.setSelectedIndex(index);
    }

    public class Renderer extends DefaultListCellRenderer
    {

        @Override
        public Component getListCellRendererComponent(
                JList list,
                Object value,
                int index,
                boolean isSelected,
                boolean cellHasFocus)
        {
            Component component = super.getListCellRendererComponent(
                    list,
                    (value instanceof TBPropertyEditor.Value) ? ((TBPropertyEditor.Value) value).visualValue : value,
                    index,
                    isSelected,
                    cellHasFocus);
            if (icons != null && index >= 0 && component instanceof JLabel)
            {
                ((JLabel) component).setIcon(icons[index]);
            }
            return component;
        }
    }

    public static class AsInt extends TBPropertyEditor
    {

//        @Override
//        public void setValue(Object arg0)
//        {
//            if (arg0 instanceof Integer)
//            {
//                super.setValue(new Color(((Integer) arg0).intValue()));
//            } else
//            {
//                super.setValue(arg0);
//            }
//        }
//
//        @Override
//        public Object getValue()
//        {
//            Object value = super.getValue();
//            if (value == null)
//            {
//                return null;
//            } else
//            {
//                return new Integer(((Color) value).getRGB());
//            }
//        }
        @Override
        protected void firePropertyChange(Object oldValue, Object newValue)
        {
//            if (oldValue instanceof Color)
//            {
//                oldValue = new Integer(((Color) oldValue).getRGB());
//            }
//            if (newValue instanceof Color)
//            {
//                newValue = new Integer(((Color) newValue).getRGB());
//            }
            super.firePropertyChange(oldValue, newValue);
        }
    }

    public static final class Value
    {

        private Object value;
        public Object visualValue;

        public Value(Object value, Object visualValue)
        {
            this.value = value;
            this.visualValue = visualValue;
        }

        @Override
        public boolean equals(Object o)
        {
            if (o == this)
            {
                return true;
            }
            if (value == o || (value != null && value.equals(o)))
            {
                return true;
            }
            return false;
        }

        @Override
        public int hashCode()
        {
            return value == null ? 0 : value.hashCode();
        }
    }
ZenHarbinger commented 8 years ago

I think the popup menu is interfering in the UI. Also, I'm still not sure what is going on, here is my assumption:

  1. - You want to edit a bean.
  2. - The bean you want to edit has a bean (a sub-bean) you want to edit.

What I usually create an editor and renderer for each type that will be present. Each editor class and renderer class registers with that type. If you add:

        private OuterClass oc = new OuterClass();

        public OuterClass getOuter() {
            return oc;
        }
        public void setOuter(OuterClass value) {
            oc = value;
        }

to the Bean class in PropertySheetPage.java the dropdown w/ the available properties show up w/ the button as well.

I was having problems with the control when the popup menu was enabled, so I removed that. I'm not sure what was going on there. When I was clicking in the left hand section of the cell, the control would immediately disappear. But if I clicked in the right, it was fine. Is this a step in the right direction? I want to help and have this tool work!

--Files-- TBPropertiesEditor.java

package com.l2fprod.common.demo;

import com.l2fprod.common.annotations.EditorRegistry;
import com.l2fprod.common.beans.editor.AbstractPropertyEditor;
import com.l2fprod.common.swing.ComponentFactory;
import com.l2fprod.common.swing.PercentLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

@EditorRegistry(type = {OuterClass.class})
public class TBPropertyEditor extends AbstractPropertyEditor {

    private final JButton button;
//   private final Renderer label;
    private Object oldValue;
    private Object selectedValue;
    private Icon[] icons;
    private final Map<String, Object> map = new HashMap();
    private JComboBox combo;

    public TBPropertyEditor() {
        combo = new JComboBox() {
            @Override
            public void setSelectedItem(Object anObject) {
                oldValue = getSelectedItem();
                super.setSelectedItem(anObject);
                selectedValue = anObject;
            }

        };
        editor = new JPanel(new PercentLayout(PercentLayout.HORIZONTAL, 0));
        ((JPanel) editor).add(combo);
        //       ((JPanel) editor).add("*", label = new Renderer());
        //       label.setOpaque(false);
        ((JPanel) editor).add(button = ComponentFactory.Helper.getFactory()
                .createMiniButton());
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                editProperties(selectedValue);
            }
        });

        ((JPanel) editor).setOpaque(true);

        combo.setRenderer(new TBPropertyEditor.Renderer());
        combo.setEditable(false);
//        combo.addPopupMenuListener(new PopupMenuListener() {
//            @Override
//            public void popupMenuCanceled(PopupMenuEvent e) {
//            }
//
//            @Override
//            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
//                TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
//            }
//
//            @Override
//            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
//
//            }
//        });
//        combo.addKeyListener(new KeyAdapter() {
//            @Override
//            public void keyPressed(KeyEvent e) {
//                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
//                    TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
//                }
//            }
//        });

        ArrayList<String> items = new ArrayList();

        List itemList = getItems();
        for (Object o : itemList) {
            items.add(o.getClass().getSimpleName());
            map.put(o.getClass().getSimpleName(), o);
        }
        setAvailableValues(items.toArray());
// combo.setSelectedIndex(-1);
    }

    protected List getItems() {
        return new ArrayList();
    }

    public void setAvailableValues(Object[] values) {
        combo.setModel(new DefaultComboBoxModel(values));
    }

    public void setAvailableIcons(Icon[] icons) {
        this.icons = icons;
    }

    protected void editProperties(Object selectedItem) {
// ResourceManager rm = ResourceManager.all(FilePropertyEditor.class);
// String title = rm.getString("TBPropertyEditor.title");
// Color selectedColor = JColorChooser.showDialog(editor, title, color);
//
// if (selectedColor != null)
// {
// Color oldColor = color;
// Color newColor = selectedColor;
//// label.setValue(newColor);
// color = newColor;
// firePropertyChange(oldColor, newColor);
// }
    }

    @Override
    public Object getValue() {
        return this.value;
//        Object selected = combo.getSelectedItem();
//        if (selected instanceof Value) {
//            return ((Value) selected).value;
//        } else {
//            return map.get((String) selected);
//        }
    }

    private Object value;

    @Override
    public void setValue(Object value) {
        if (value == null) {
            return;
        } else {
            this.value = value;
        }
        try {
            PropertyDescriptor[] cbProps = Introspector.getBeanInfo(value.getClass()).getPropertyDescriptors();

            ArrayList<String> items = new ArrayList<>();
            for(PropertyDescriptor dp : cbProps) {
                if(dp.getReadMethod() != null && dp.getWriteMethod() != null) {
                    items.add(dp.getName());
                }
            }
            setAvailableValues(items.toArray());
//            Object current = null;
//            int index = -1;
//            for (int i = 0, c = combo.getModel().getSize(); i < c; i++) {
//                current = combo.getModel().getElementAt(i);
//                if (value == current || (current != null && current.equals(value))) {
//                    index = i;
//                    break;
//                }
//            }
//            combo.setSelectedIndex(index);
        } catch (IntrospectionException ex) {
            Logger.getLogger(TBPropertyEditor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public class Renderer extends DefaultListCellRenderer {

        @Override
        public Component getListCellRendererComponent(
                JList list,
                Object value,
                int index,
                boolean isSelected,
                boolean cellHasFocus) {
            Component component = super.getListCellRendererComponent(
                    list,
                    (value instanceof TBPropertyEditor.Value) ? ((TBPropertyEditor.Value) value).visualValue : value,
                    index,
                    isSelected,
                    cellHasFocus);
            if (icons != null && index >= 0 && component instanceof JLabel) {
                ((JLabel) component).setIcon(icons[index]);
            }
            return component;
        }
    }

    public static class AsInt extends TBPropertyEditor {
// @Override
// public void setValue(Object arg0)
// {
// if (arg0 instanceof Integer)
// {
// super.setValue(new Color(((Integer) arg0).intValue()));
// } else
// {
// super.setValue(arg0);
// }
// }
//
// @Override
// public Object getValue()
// {
// Object value = super.getValue();
// if (value == null)
// {
// return null;
// } else
// {
// return new Integer(((Color) value).getRGB());
// }
// }

        @Override
        protected void firePropertyChange(Object oldValue, Object newValue) {
// if (oldValue instanceof Color)
// {
// oldValue = new Integer(((Color) oldValue).getRGB());
// }
// if (newValue instanceof Color)
// {
// newValue = new Integer(((Color) newValue).getRGB());
// }
            super.firePropertyChange(oldValue, newValue);
        }
    }

    public static final class Value {

        private Object value;
        public Object visualValue;

        public Value(Object value, Object visualValue) {
            this.value = value;
            this.visualValue = visualValue;
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }
            if (value == o || (value != null && value.equals(o))) {
                return true;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return value == null ? 0 : value.hashCode();
        }
    }
}

OuterClass.java

package com.l2fprod.common.demo;
public class OuterClass {
    private int i;
    private InnerClass d;

    public int getIntegerValue() {
        return i;
    }

    public void setIntegerValue(int value) {
        i = value;
    }

    public InnerClass getInnerBean() {
        return d;
    }

    public void setInnerBean(InnerClass value) {
        d = value;
    }
}

InnerClass.java

package com.l2fprod.common.demo;
public class InnerClass {
   private boolean x;

   public boolean getBooleanValue() {
       return x;
   }

   public void setBooleanValue(boolean value) {
       x = value;
   }
}
tonybeckett commented 8 years ago

I have pushed changes to my fork of this repository that added a bean(as in seed) property that shows the issue.

ZenHarbinger commented 8 years ago

I have to commend out the following line:

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
            {
//                TBPropertyEditor.this.firePropertyChange(oldValue, combo.getSelectedItem());
            }

This is causing the combo box to disappear too soon (goes from editing to rendering mode). However, I am getting that the combo box will initially show-up, drop-down, and then pull back up, but will still be rendered for future selection. I don't know why this is the case.

I am running Oracle Java8 w/ Linux. I don't have access to any other platforms for testing.

I also changed this line in your setValues() method:

if (value == current || (current != null && current.equals(value.getClass().getSimpleName()))) {

as the previous check for equals(...) wasn't sufficient since current is a String and value is an Object of type Bean.

tonybeckett commented 8 years ago

I am running on Windows 10. Selecting the combobox does nothing. I have to select the button or the void area to the right to get the selection to show up.

ZenHarbinger commented 8 years ago

Unfortunately, I do not have windows 10, so I can't test this. All I can say is that it does work w/ Linux. But the behavior you had is what I was experiencing until I took out the pop-up menu behavior.

ZenHarbinger commented 8 years ago

OK, so I was playing around with trying to fix the appear/disappear on first drop-down. Try substituting in this code in your TBPropertyEditor.java file

        final java.util.concurrent.atomic.AtomicBoolean first = new java.util.concurrent.atomic.AtomicBoolean(true);
        combo.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                if (first.get()) {
                    first.set(false);
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            combo.showPopup();
                        }
                    });
                }
            }

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            }
        });
ZenHarbinger commented 8 years ago

Still has some issues if the combo box is not the first item selected, then this will lead to it being re-opened the first time. It seems to be an event firing that I don't know that is causing this. If you find a fix, let me know.

ZenHarbinger commented 8 years ago

You can try adding something that will check to see if the events are firing too close together: This says that if less than 75 milliseconds happened between the viz, and inviz, fire the viz again.

        final java.util.concurrent.atomic.AtomicBoolean first = new java.util.concurrent.atomic.AtomicBoolean(true);
        final java.util.concurrent.atomic.AtomicLong vis = new java.util.concurrent.atomic.AtomicLong(0);
        final java.util.concurrent.atomic.AtomicLong invis = new java.util.concurrent.atomic.AtomicLong(0);
        combo.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                invis.set(Calendar.getInstance().getTimeInMillis());
//                System.out.println(invis.get() - vis.get());
                long threshold = 75;
                if (invis.get() - vis.get() < threshold) {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            combo.showPopup();
                        }
                    });
                }
                if (first.get()) {
                    first.set(false);
                }
            }

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                vis.set(Calendar.getInstance().getTimeInMillis());
            }
        });