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

jaxx.runtime.swing.editor.bean.BeanUIUtil Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-1
Show newest version
/*
 * #%L
 * JAXX :: Widgets
 * %%
 * Copyright (C) 2008 - 2014 Code Lutin, Tony Chemit
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */
package jaxx.runtime.swing.editor.bean;

import jaxx.runtime.swing.JAXXButtonGroup;
import jaxx.runtime.swing.JAXXRuntimeException;
import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.autocomplete.AutoCompleteComboBoxEditor;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.jdesktop.swingx.autocomplete.AutoCompleteDocument;
import org.jdesktop.swingx.autocomplete.ComboBoxAdaptor;
import org.jdesktop.swingx.autocomplete.ObjectToStringConverter;
import org.nuiton.decorator.Decorator;
import org.nuiton.decorator.DecoratorUtil;
import org.nuiton.decorator.JXPathDecorator;
import org.nuiton.decorator.MultiJXPathDecorator;

import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxEditor;
import javax.swing.InputMap;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyListener;
import java.beans.Introspector;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static org.nuiton.i18n.I18n.t;
import static org.nuiton.i18n.I18n.n;

/**
 * Class with usefull methods used in bean uis.
 *
 * @author Tony Chemit - [email protected]
 * @since 2.2
 */
public class BeanUIUtil {

    public static final String DEFAULT_POPUP_LABEL = n("bean.popup.label");

    public static final String DEFAULT_SELECTED_TOOLTIP = n("bean.sort.on");

    public static final String DEFAULT_NOT_SELECTED_TOOLTIP = n("bean.sort.off");

    public static final Object[] EMPTY_CLASS_ARRAY = new Object[0];

    public static void invokeMethod(Method mut, Object source, Object... params) {

        if (mut != null) {
            try {
                mut.invoke(source, params);
            } catch (IllegalAccessException e) {
                throw new JAXXRuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new JAXXRuntimeException(e.getCause());
            }
        }
    }

    /**
     * Encapsule un {@link Decorator} dans un {@link ObjectToStringConverter}.
     *
     * @param decorator le decorateur a encapsuler.
     * @return le converter encapsule dans un {@link ObjectToStringConverter}
     */
    public static ObjectToStringConverter newDecoratedObjectToStringConverter(final Decorator decorator) {

        return new ObjectToStringConverter() {

            @Override
            public String getPreferredStringForItem(Object item) {
                return item instanceof String ? (String) item : item == null ? "" : decorator.toString(item);
            }
        };
    }

    /**
     * Ajout l'auto-complétion sur une liste déroulante, en utilisant le
     * converteur donné pour afficher les données.
     *
     * @param combo     la combo à décorer
     * @param convertor le converter utilisé pour afficher les données.
     */
    public static void decorate(JComboBox combo, ObjectToStringConverter convertor) {

        // tchemit 2010-10-05 since swingx 1.6.2, undecorate stuff is no more
        // public and we want to use it, so hack it...
        combo.putClientProperty("oldEditor", combo.getEditor());
        combo.putClientProperty("oldDocument", combo.getEditor().getEditorComponent());

        AutoCompleteDecorator.decorate(combo, convertor);
    }

    /**
     * Désactive l'aut-complétion sur une liste déroulante, en y repositionnant
     * le modèle du document d'édition d'avant auto-complétion.
     *
     * @param comboBox         la liste déroulante à décorer
     * @param originalDocument le document original de l'édtieur de la
     *                         liste déroulante.
     */
    public static void undecorate(JComboBox comboBox, Document originalDocument) {

        // has not to be editable
        comboBox.setEditable(false);

        // configure the text component=editor component
        Component c = comboBox.getEditor().getEditorComponent();
        JTextComponent editorComponent = (JTextComponent) c;
        editorComponent.setDocument(originalDocument);
        editorComponent.setText(null);
        //undecorate(comboBox);

        //remove old property change listener
        //for (PropertyChangeListener l : c.getPropertyChangeListeners("editor")) {
        //    if (l instanceof AutoCompletePropertyChangeListener) {
        //        c.removePropertyChangeListener("editor", l);
        //    }
        //}

        ComboBoxEditor oldEditor = (ComboBoxEditor) comboBox.getClientProperty("oldEditor");

        if (editorComponent.getDocument() instanceof AutoCompleteDocument) {
            AutoCompleteDocument doc = (AutoCompleteDocument) editorComponent.getDocument();

            if (doc.isStrictMatching()) {
                ActionMap map = comboBox.getActionMap();

                for (String key : COMBO_BOX_ACTIONS) {
                    map.put(key, null);
                }
            }

            //remove old property change listener
            for (PropertyChangeListener l : comboBox.getPropertyChangeListeners("editor")) {
                //if (l instanceof AutoComplete.PropertyChangeListener) {
                if (l.getClass().getName().contains("AutoComplete")) {
                    comboBox.removePropertyChangeListener("editor", l);
                }
            }

            for (PropertyChangeListener l : comboBox.getPropertyChangeListeners("enabled")) {
                //if (l instanceof AutoComplete.PropertyChangeListener) {
                if (l.getClass().getName().contains("AutoComplete")) {
                    comboBox.removePropertyChangeListener("enabled", l);
                }
            }

            AutoCompleteComboBoxEditor editor = (AutoCompleteComboBoxEditor) comboBox.getEditor();
            comboBox.setEditor(oldEditor);

            //remove old key listener
            for (KeyListener l : editorComponent.getKeyListeners()) {
                //if (l instanceof AutoComplete.KeyAdapter) {
                if (l.getClass().getName().contains("AutoComplete")) {
                    editorComponent.removeKeyListener(l);
                    break;
                }
            }

            undecorate(editorComponent, originalDocument);

            for (ActionListener l : comboBox.getActionListeners()) {
                if (l instanceof ComboBoxAdaptor) {
                    comboBox.removeActionListener(l);
                    break;
                }
            }

            //TODO remove aqua fix

            //TODO reset editibility
        }
    }

    static void undecorate(JTextComponent textComponent, Document originalDocument) {
        Document doc = textComponent.getDocument();

        if (doc instanceof AutoCompleteDocument) {
            //remove autocomplete key/action mappings
            InputMap map = textComponent.getInputMap();

            while (map.getParent() != null) {
                InputMap parent = map.getParent();

                //if (parent instanceof AutoComplete.InputMap) {
                if (parent.getClass().getName().contains("AutoComplete")) {
                    map.setParent(parent.getParent());
                }

                map = parent;
            }

            textComponent.getActionMap().put("nonstrict-backspace", null);

            //remove old focus listener
            for (FocusListener l : textComponent.getFocusListeners()) {
                //if (l instanceof AutoComplete.FocusAdapter) {
                if (l.getClass().getName().contains("AutoComplete")) {
                    textComponent.removeFocusListener(l);
                    break;
                }
            }

            //reset to original document
            //textComponent.setDocument(((AutoCompleteDocument) doc).delegate);
            textComponent.setDocument(originalDocument);
        }
    }

    public static  MultiJXPathDecorator createDecorator(JXPathDecorator decorator) {
        if (decorator == null) {
            throw new NullPointerException(
                    "can not have a null decorator as parameter");
        }
        String separator;
        String separatorReplacement;

        if (decorator instanceof Cloneable) {
            Cloneable cloneable = (Cloneable) decorator;

            try {
                Object clone = MethodUtils.invokeExactMethod(cloneable,
                                                             "clone",
                                                             EMPTY_CLASS_ARRAY
                );
                return (MultiJXPathDecorator) clone;
            } catch (Exception e) {
                throw new IllegalStateException("Could not clone decorator " + decorator, e);
            }

        }
        if (decorator instanceof MultiJXPathDecorator) {

            separator = ((MultiJXPathDecorator) decorator).getSeparator();
            separatorReplacement = ((MultiJXPathDecorator) decorator).getSeparatorReplacement();

        } else {

            separator = "??" + new Date().getTime();
            separatorReplacement = " - ";
        }

        return DecoratorUtil.newMultiJXPathDecorator(
                decorator.getType(),
                decorator.getInitialExpression(),
                separator,
                separatorReplacement
        );
    }

    public static abstract class PopupHandler implements Runnable {

        public static final Log log = LogFactory.getLog(PopupHandler.class);

        public abstract JPopupMenu getPopup();

        public abstract JComponent getInvoker();

        @Override
        public void run() {

            updatePopup();

            Dimension dim = getPopup().getPreferredSize();

            JComponent invoker = getInvoker();
            getPopup().show(
                    invoker,
                    (int) (invoker.getPreferredSize().getWidth() - dim.getWidth()),
                    invoker.getHeight()
            );
        }

        /** Toggle the popup visible state. */
        public void togglePopup() {
            boolean newValue = !getPopup().isVisible();

            if (log.isTraceEnabled()) {
                log.trace(newValue);
            }

            if (!newValue) {
                if (getPopup() != null) {
                    getPopup().setVisible(false);
                }
                return;
            }
            SwingUtilities.invokeLater(this);
        }

        protected void updatePopup() {
            getPopup().pack();
        }

        /**
         * Creation de l'ui pour modifier le décorateur.
         *
         * @param selectedTip
         * @param notSelectedTip
         * @param i18nPrefix
         * @param title
         * @param indexes
         * @param popupLabel
         * @param sortUp
         * @param sortDown
         * @param decorator      le decorateur a utiliser
         */
        public void preparePopup(String selectedTip,
                                    String notSelectedTip,
                                    String i18nPrefix,
                                    String title,
                                    ButtonGroup indexes,
                                    JSeparator popupSeparator,
                                    JLabel popupLabel,
                                    AbstractButton sortUp,
                                    AbstractButton sortDown,
                                    MultiJXPathDecorator decorator) {
            if (selectedTip == null) {
                // use default selected tip text
                selectedTip = DEFAULT_SELECTED_TOOLTIP;
            }
            if (notSelectedTip == null) {
                // use default selected tip text
                notSelectedTip = DEFAULT_NOT_SELECTED_TOOLTIP;
            }
            JPopupMenu popup = getPopup();

            //Container container = ui.getIndexesContainer();

            int nbContext = decorator.getNbContext();
            if (nbContext > 1) {
                for (int i = 0; i < nbContext; i++) {
                    String property = i18nPrefix + decorator.getProperty(i);
                    String propertyI18n = t(property);
                    JRadioButtonMenuItem button = new JRadioButtonMenuItem(propertyI18n);
                    button.putClientProperty(JAXXButtonGroup.BUTTON8GROUP_CLIENT_PROPERTY, indexes);
                    button.putClientProperty(JAXXButtonGroup.VALUE_CLIENT_PROPERTY, i);
                    popup.add(button);
                    if (selectedTip != null) {
                        button.putClientProperty(JAXXButtonGroup.SELECTED_TIP_CLIENT_PROPERTY, t(selectedTip, propertyI18n));
                    }
                    if (notSelectedTip != null) {
                        button.putClientProperty(JAXXButtonGroup.NOT_SELECTED_TIP_CLIENT_PROPERTY, t(notSelectedTip, propertyI18n));
                    }
                    button.setSelected(false);
                    indexes.add(button);
                }
            }
            if (title == null) {
                // use default popup title
                title = DEFAULT_POPUP_LABEL;

                Class type = decorator.getType();
                String beanI18nKey;
                if (type == null) {
                    beanI18nKey = n("bean.unknown.type");
                } else {
                    beanI18nKey = i18nPrefix + Introspector.decapitalize(type.getSimpleName());
                }
                String beanI18n = t(beanI18nKey);
                title = t(title, beanI18n);
            } else {
                title = t(title);
            }

            sortDown.putClientProperty(JAXXButtonGroup.SELECTED_TIP_CLIENT_PROPERTY, t("bean.sort.down.tip"));
            sortDown.putClientProperty(JAXXButtonGroup.NOT_SELECTED_TIP_CLIENT_PROPERTY, t("bean.sort.down.toSelect.tip"));

            sortUp.putClientProperty(JAXXButtonGroup.SELECTED_TIP_CLIENT_PROPERTY, t("bean.sort.up.tip"));
            sortUp.putClientProperty(JAXXButtonGroup.NOT_SELECTED_TIP_CLIENT_PROPERTY, t("bean.sort.up.toSelect.tip"));

            if (nbContext < 2) {
                getPopup().remove(popupSeparator);
                getPopup().remove(popupLabel);
            }
            popupLabel.setText(title);
            getPopup().setLabel(title);
            getPopup().invalidate();
        }
    }


    //these keys were pulled from BasicComboBoxUI from Sun JDK 1.6.0_20

    private static final List COMBO_BOX_ACTIONS = unmodifiableList(asList("selectNext",
                                                                                  "selectNext2", "selectPrevious", "selectPrevious2", "pageDownPassThrough",
                                                                                  "pageUpPassThrough", "homePassThrough", "endPassThrough"));
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy