All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.danilopianini.view.ObjectModFrame Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (C) 2009, 2015, Danilo Pianini and contributors
 * listed in the project's build.gradle or pom.xml file.
 *
 * This file is distributed under the terms of the Apache License, version 2.0
 *******************************************************************************/
package org.danilopianini.view;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.danilopianini.lang.CollectionWithCurrentElement;
import org.danilopianini.lang.RangedInteger;

/**
 */
public class ObjectModFrame extends JFrame {

    private static final long serialVersionUID = 5816449414660853953L;

    private String actionCommand = "";
    private final AtomicInteger idgen = new AtomicInteger();
    private final List listeners = Collections.synchronizedList(new ArrayList());

    private class BooleanGUIComponent extends NoRefGUIComponent implements ActionListener {
        private static final long serialVersionUID = -7149307455972615932L;
        private final JCheckBox box;

        BooleanGUIComponent(final String name, final Object obj, final Field f) throws IllegalAccessException {
            super(obj, f);
            box = new JCheckBox(name, getField().getBoolean(getObject()));
            add(box);
            box.addActionListener(this);
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
            update(box.isSelected());
        }
    }

    private class EnumGUIComponent extends NoRefGUIComponent implements ActionListener {
        private static final long serialVersionUID = 3329099673991014137L;
        private final JComboBox box;

        EnumGUIComponent(final String name, final Object obj, final Field f) throws IllegalAccessException {
            super(obj, f);
            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            box = new JComboBox(f.getType().getEnumConstants());
            box.setSelectedItem(f.get(obj));
            add(new JLabel(name));
            add(Box.createHorizontalStrut(10));
            add(box);
            box.addActionListener(this);
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
            update(box.getSelectedItem());
        }
    }

    private class CurrentCollectionGUIComponent extends NoRefGUIComponent implements ActionListener {
        private static final long serialVersionUID = 6031819767955928108L;
        private final JComboBox box;
        private final CollectionWithCurrentElement col;

        @SuppressWarnings("unchecked")
        CurrentCollectionGUIComponent(final String name, final Object obj, final Field f) throws IllegalAccessException {
            super(obj, f);
            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            col = (CollectionWithCurrentElement) f.get(obj);
            box = new JComboBox(col.toArray());
            box.setSelectedItem(col.getCurrent());
            add(new JLabel(name));
            add(Box.createHorizontalStrut(10));
            add(box);
            box.addActionListener(this);
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
            col.setCurrent(box.getSelectedItem());
        }
    }

    private abstract class NoRefGUIComponent extends JPanel {
        private static final long serialVersionUID = 7131340105783363250L;
        private final Field field;
        private final Object object;

        NoRefGUIComponent(final Object obj, final Field f) {
            super();
            field = f;
            object = obj;
        }

        protected Field getField() {
            return field;
        }

        protected Object getObject() {
            return object;
        }

        protected void update(final Object fieldVal) {
            try {
                field.set(object, fieldVal);
                notifyListeners();
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace(); // NOPMD by danysk on 1/23/15 6:40 PM
            }
        }
    }

    private class RangedIntegerGUIComponent extends JPanel implements ChangeListener {
        private static final long serialVersionUID = 4082696268464803253L;
        public static final int SLIDERS_MAJOR_TICKS = 10;
        private final RangedInteger n;
        private final JSlider slider = new JSlider();
        private final JLabel label = new JLabel();

        RangedIntegerGUIComponent(final String name, final RangedInteger num) {
            super();
            n = num;
            add(new JLabel(name));
            add(slider);
            add(label);
            final int min = n.getMin();
            final int max = n.getMax();
            slider.setMaximum(max);
            slider.setMinimum(min);
            slider.setMinorTickSpacing(1);
            slider.setMajorTickSpacing((max - min) / SLIDERS_MAJOR_TICKS);
            slider.setValue(n.getVal());
            label.setText(Integer.toString(n.getVal()));
            slider.addChangeListener(this);
        }

        @Override
        public void stateChanged(final ChangeEvent e) {
            n.setVal(slider.getValue());
            label.setText(Integer.toString(n.getVal()));
            pack();
            notifyListeners();
        }
    }

    private class StringGUIComponent extends NoRefGUIComponent implements DocumentListener {
        private static final long serialVersionUID = -1557933366572097377L;
        private final JTextField text;

        StringGUIComponent(final String name, final Object obj, final Field f) throws IllegalAccessException {
            super(obj, f);
            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            add(new JLabel(name));
            add(Box.createHorizontalStrut(10));
            text = new JTextField(f.get(obj).toString());
            add(text);
            text.getDocument().addDocumentListener(this);
        }

        @Override
        public void changedUpdate(final DocumentEvent e) {
            update();
        }

        @Override
        public void insertUpdate(final DocumentEvent e) {
            update();
        }

        @Override
        public void removeUpdate(final DocumentEvent e) {
            update();
        }

        private void update() {
            update(text.getText());
        }

    }

    private static boolean assignable(final Class clazz, final Field field) throws IllegalAccessException {
        return clazz.isAssignableFrom(field.getType());
    }

    /**
     * @param obj
     *            the object to inspect
     * @throws IllegalAccessException
     *             if the fields supposed to be managed by the graphical
     *             interface are not accessible
     */
    public ObjectModFrame(final Object obj) throws IllegalAccessException {
        super();
        setType(Type.POPUP);
        setAlwaysOnTop(true);
        final JPanel contents = new JPanel();
        contents.setBorder(new LineBorder(new Color(0, 0, 0)));
        addMouseListener(new CloseFrameWhenMouseExits(this));
        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
        for (final Field f : getAllFields(new ArrayList(), obj.getClass())) {
            if (f.isAnnotationPresent(ExportForGUI.class)) {
                final String expName = f.getAnnotation(ExportForGUI.class).nameToExport();
                final boolean accessible = f.isAccessible();
                if (!accessible) {
                    setAccess(f, true);
                }
                if (assignable(RangedInteger.class, f)) {
                    contents.add(new RangedIntegerGUIComponent(expName, (RangedInteger) f.get(obj)));
                } else if (Boolean.class.equals(f.getType()) || f.getType().equals(Boolean.TYPE)) {
                    contents.add(new BooleanGUIComponent(expName, obj, f));
                } else if (String.class.equals(f.getType())) {
                    contents.add(new StringGUIComponent(expName, obj, f));
                } else if (f.getType().isEnum()) {
                    contents.add(new EnumGUIComponent(expName, obj, f));
                } else if (CollectionWithCurrentElement.class.isAssignableFrom(f.getType())) {
                    contents.add(new CurrentCollectionGUIComponent(expName, obj, f));
                } else {
                    setAccess(f, accessible);
                }
            }
        }
        getContentPane().add(contents);
        pack();
    }

    private static void setAccess(final Field f, final boolean accessible) {
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Void run() {
                f.setAccessible(accessible);
                return null;
            }
        });
    }

    /**
     * Adds an ActionListener to the frame's components.
     * 
     * @param l
     *            the ActionListener to be added
     */
    public void addActionListener(final ActionListener l) {
        listeners.add(l);
    }

    /**
     * Returns the action command for this frame's components.
     * 
     * @return the action command for this frame's components
     */
    public String getActionCommand() {
        return actionCommand;
    }

    private void notifyListeners() {
        final ActionEvent e = new ActionEvent(this, idgen.getAndIncrement(), actionCommand);
        for (final ActionListener a : listeners) {
            a.actionPerformed(e);
        }
    }

    /**
     * Sets the action command for this frame's components.
     * 
     * @param ac
     *            the action command for this frame's components
     */
    public void setActionCommand(final String ac) {
        this.actionCommand = ac;
    }

    private static List getAllFields(final List fields, final Class type) {
        if (type.getSuperclass() != null) {
            getAllFields(fields, type.getSuperclass());
        }
        for (final Field field: type.getDeclaredFields()) {
            fields.add(field);
        }
        return fields;
    }

}