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

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

/*
 * #%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.SwingUtil;
import jaxx.runtime.swing.JAXXButtonGroup;
import jaxx.runtime.swing.renderer.DecoratorListCellRenderer;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.autocomplete.ObjectToStringConverter;
import org.nuiton.decorator.DecoratorUtil;
import org.nuiton.decorator.JXPathDecorator;
import org.nuiton.decorator.MultiJXPathDecorator;
import org.nuiton.util.beans.BeanUtil;

import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * Le handler d'un {@link BeanComboBox}.
 *
 * Note: ce handler n'est pas stateless et n'est donc pas partageable entre plusieurs ui.
 *
 * @param  le type des objet contenus dans le modèle du composant.
 * @author Tony Chemit - [email protected]
 * @see BeanComboBox
 */
public class BeanComboBoxHandler implements PropertyChangeListener {

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

    /** ui if the handler */
    protected BeanComboBox ui;

    /** the mutator method on the property of boxed bean in the ui */
    protected Method mutator;

    /** the original document of the combbo box editor (keep it to make possible undecorate) */
    protected Document originalDocument;

    /** the convertor used to auto-complete */
    protected ObjectToStringConverter convertor;

    /** the decorator of data */
    protected MultiJXPathDecorator decorator;

    protected boolean init;

    public BeanComboBoxHandler(BeanComboBox ui) {
        this.ui = ui;
    }

    protected final FocusListener EDITOR_TEXT_COMP0NENT_FOCUSLISTENER = new FocusListener() {

        @Override
        public void focusGained(FocusEvent e) {
            if (log.isDebugEnabled()) {
                log.debug("close popup from " + e);
            }
            ui.getPopup().setVisible(false);
        }

        @Override
        public void focusLost(FocusEvent e) {
        }
    };

    private final BeanUIUtil.PopupHandler popupHandler = new BeanUIUtil.PopupHandler() {

        @Override
        public JPopupMenu getPopup() {
            return ui.getPopup();
        }

        @Override
        public JComponent getInvoker() {
            return ui.getChangeDecorator();
        }
    };

    /**
     * Initialise le handler de l'ui
     *
     * @param decorator le decorateur a utiliser
     * @param data      la liste des données a gérer
     */
    public void init(JXPathDecorator decorator, List data) {

        if (init) {
            throw new IllegalStateException("can not init the handler twice");
        }
        init = true;

        if (decorator == null) {
            throw new NullPointerException("decorator can not be null (for type " + ui.getBeanType() + ")");
        }

        JAXXButtonGroup indexes = ui.getIndexes();

        this.decorator = BeanUIUtil.createDecorator(decorator);

        final JComboBox combobox = ui.getCombobox();

        // init combobox renderer base on given decorator
        combobox.setRenderer(new DecoratorListCellRenderer(this.decorator));
        combobox.addPopupMenuListener(new PopupMenuListener() {

            private O selectedItem;

            boolean canceled = false;

            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                selectedItem = (O) combobox.getSelectedItem();
            }

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                O newSelectedItem = (O) combobox.getSelectedItem();
                if (canceled) {
                    ui.setSelectedItem(null);

                } else if ((newSelectedItem == null ^ selectedItem == null)
                           || (newSelectedItem != null
                               && !selectedItem.equals(newSelectedItem))) {
                    ui.setSelectedItem(newSelectedItem);
                }
                selectedItem = null;
                canceled = false;
            }

            public void popupMenuCanceled(PopupMenuEvent e) {
                canceled = true;
            }
        });

        convertor = BeanUIUtil.newDecoratedObjectToStringConverter(this.decorator);

        // keep a trace of original document (to make possible reverse autom-complete) 
        JTextComponent editorComponent = (JTextComponent) combobox.getEditor().getEditorComponent();
        originalDocument = editorComponent.getDocument();

        // build popup
        popupHandler.preparePopup(ui.getSelectedToolTipText(),
                                  ui.getNotSelectedToolTipText(),
                                  ui.getI18nPrefix(),
                                  ui.getPopupTitleText(),
                                  indexes,
                                  ui.getPopupSeparator(),
                                  ui.getPopupLabel(),
                                  ui.getSortUp(),
                                  ui.getSortDown(),
                                  this.decorator);

        ui.autoComplete = true;

        ui.addPropertyChangeListener(this);

        // set datas
        ui.setData(data);

        // select sort button
        indexes.setSelectedButton(ui.getIndex());
    }

    /** Toggle the popup visible state. */
    public void togglePopup() {
        popupHandler.togglePopup();
    }

    /**
     * @return {@code true} if there is no data in comboBox,
     * {@code false} otherwise.
     * @since 2.5.9
     */
    public boolean isEmpty() {
        return CollectionUtils.isEmpty(ui.getData());
    }

    /**
     * Add the given items into the comboBox.
     *
     * Note: The item will be inserted at his correct following
     * the selected ordering.
     *
     * @param items items to add in comboBox.
     * @since 2.5.28
     */
    public void addItems(Iterable items) {

        List data = ui.getData();

        boolean wasEmpty = CollectionUtils.isEmpty(data);

        for (O item : items) {
            data.add(item);
        }

        updateUI(ui.getIndex(), ui.isReverseSort());

        fireEmpty(wasEmpty);
    }

    /**
     * Remove the given items from the comboBox model.
     *
     * Note: If this item was selected, then selection will be
     * cleared.
     *
     * @param items items to remove from the comboBox model
     * @since 2.5.28
     */
    public void removeItems(Iterable items) {

        List data = ui.getData();

        boolean needUpdate = false;
        for (O item : items) {
            boolean remove = data.remove(item);

            if (remove) {

                // item was found in data

                Object selectedItem = ui.getSelectedItem();
                if (item == selectedItem) {

                    // item was selected item, reset selected item then
                    ui.setSelectedItem(null);
                }

                needUpdate = true;

            }
        }

        if (needUpdate) {

            updateUI(ui.getIndex(), ui.isReverseSort());
            fireEmpty(false);
        }

    }

    /**
     * Add the given item into the comboBox.
     *
     * Note: The item will be inserted at his correct following
     * the selected ordering.
     *
     * @param item item to add in comboBox.
     * @since 2.5.9
     */
    public void addItem(O item) {

        addItems(Collections.singleton(item));
    }

    /**
     * Remove the given item from the comboBox model.
     *
     * Note: If this item was selected, then selection will be
     * cleared.
     *
     * @param item the item to remove from the comboBox model
     * @since 2.5.9
     */
    public void removeItem(O item) {

        removeItems(Collections.singleton(item));
    }

    /**
     * Sort data of the model.
     *
     * @since 2.5.10
     */
    public void sortData() {

        // just update UI should do the math of this
        updateUI(ui.getIndex(), ui.isReverseSort());
    }

    /**
     * Focus combo only if autoFocus ui property is on.
     *
     * @since 2.8.5
     */
    public void focusCombo() {
        if (ui.isAutoFocus()) {
            ui.combobox.requestFocusInWindow();
        }
    }

    /**
     * Modifie l'état autoComplete de l'ui.
     *
     * @param oldValue l'ancienne valeur
     * @param newValue la nouvelle valeur
     */
    protected void setAutoComplete(Boolean oldValue, Boolean newValue) {
        oldValue = oldValue != null && oldValue;
        newValue = newValue != null && newValue;
        if (oldValue.equals(newValue)) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug("autocomplete state : <" + oldValue + " to " + newValue + ">");
        }
        if (!newValue) {
            JTextComponent editorComponent = (JTextComponent) ui.getCombobox().getEditor().getEditorComponent();
            editorComponent.removeFocusListener(EDITOR_TEXT_COMP0NENT_FOCUSLISTENER);
            BeanUIUtil.undecorate(ui.getCombobox(), originalDocument);
        } else {
            BeanUIUtil.decorate(ui.getCombobox(), convertor);
            JTextComponent editorComponent = (JTextComponent) ui.getCombobox().getEditor().getEditorComponent();
            editorComponent.addFocusListener(EDITOR_TEXT_COMP0NENT_FOCUSLISTENER);
        }
    }

    /**
     * Modifie l'index du décorateur
     *
     * @param oldValue l'ancienne valeur
     * @param newValue la nouvelle valeur
     */
    protected void setIndex(Integer oldValue, Integer newValue) {
        if (newValue == null || newValue.equals(oldValue)) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug("check state : <" + oldValue + " to " + newValue + ">");
        }
        updateUI(newValue, ui.isReverseSort());
    }

    /**
     * Modifie l'index du décorateur
     *
     * @param oldValue l'ancienne valeur
     * @param newValue la nouvelle valeur
     */

    protected void setSortOrder(Boolean oldValue, Boolean newValue) {

        if (newValue == null || newValue.equals(oldValue)) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug("check state : <" + oldValue + " to " + newValue + ">");
        }

        updateUI(ui.getIndex(), newValue);
    }

    protected void updateUI(int index, boolean reversesort) {

        // change decorator context
        decorator.setContextIndex(index);

        // keep selected item
        Object previousSelectedItem = ui.getSelectedItem();
        Boolean wasAutoComplete = ui.isAutoComplete();

        if (wasAutoComplete) {
            ui.setAutoComplete(false);
        }

        // remove autocomplete
        if (previousSelectedItem != null) {
            ui.getCombobox().setSelectedItem(null);
            ui.selectedItem = null;
        }

        List data = ui.getData();
        if (ui.isSortable()) {
            try {
                // Sort data with the decorator jxpath tokens.
                DecoratorUtil.sort(decorator,
                                   data,
                                   index,
                                   reversesort);

            } catch (Exception eee) {
                log.warn(eee.getMessage(), eee);
            }
        }

        // reload the model
        SwingUtil.fillComboBox(ui.getCombobox(), data, null);

        if (wasAutoComplete) {
            ui.setAutoComplete(true);
        }

        if (previousSelectedItem != null) {
            ui.setSelectedItem(previousSelectedItem);
        }

        ui.getCombobox().requestFocus();
    }

    /**
     * Modifie la valeur sélectionnée dans la liste déroulante.
     *
     * @param oldValue l'ancienne valeur
     * @param newValue la nouvelle valeur
     */
    protected void setSelectedItem(O oldValue, O newValue) {
        if (ui.getBean() == null) {
            return;
        }

        if (newValue == null) {
            if (ui.getCombobox().getSelectedItem() == null) {
                return;
            }
            ui.getCombobox().setSelectedItem(null);

            if (ui.isAutoComplete()) {
                ui.setAutoComplete(false);
                ui.setAutoComplete(true);
            }

            if (oldValue == null) {
                return;
            }
        }
        if (log.isDebugEnabled()) {
            log.debug(ui.getProperty() + " on " + getBeanType() + " :: " + oldValue + " to " + newValue);
        }

        BeanUIUtil.invokeMethod(getMutator(), ui.getBean(), (O) newValue);
    }

    /** @return le document de l'éditeur avant complétion. */
    public Document getOriginalDocument() {
        return originalDocument;
    }

    public MultiJXPathDecorator getDecorator() {
        return decorator;
    }

    /**
     * @return get the type of objects contained in the comboBox model.
     * @since 2.5.9
     */
    public Class getBeanType() {
        Class result = ui.getBeanType();
        if (result == null) {
            result = decorator == null ? null : decorator.getType();
        }
        return result;
    }

    /**
     * Obtain the type of objects contained in the comboBox using the model mutator.
     *
     * @return get the type of objects contained in the comboBox model.
     * @deprecated since 2.5.9 (use now method {@link #getBeanType()})
     */
    @Deprecated
    public Class getTargetClass() {
        Method m = getMutator();
        return m == null ? null : m.getParameterTypes()[0];
    }

    /** @return le mutateur a utiliser pour modifier le bean associé. */
    protected Method getMutator() {
        if (mutator == null && ui.getBean() != null && ui.getProperty() != null) {
            mutator = BeanUtil.getMutator(ui.getBean(), ui.getProperty());
        }
        return mutator;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        String propertyName = evt.getPropertyName();

        if (BeanComboBox.PROPERTY_SELECTED_ITEM.equals(propertyName)) {
            setSelectedItem((O) evt.getOldValue(), (O) evt.getNewValue());
            return;
        }

        if (BeanComboBox.PROPERTY_AUTO_COMPLETE.equals(propertyName)) {

            setAutoComplete((Boolean) evt.getOldValue(),
                            (Boolean) evt.getNewValue());
            return;
        }

        if (BeanListHeader.PROPERTY_INDEX.equals(propertyName)) {

            // decorator index has changed, force reload of data in ui
            setIndex((Integer) evt.getOldValue(),
                     (Integer) evt.getNewValue());
            return;
        }

        if (BeanListHeader.PROPERTY_REVERSE_SORT.equals(propertyName)) {

            // sort order has changed, force reload of data in ui
            setSortOrder((Boolean) evt.getOldValue(),
                         (Boolean) evt.getNewValue());
            return;
        }

        if (BeanListHeader.PROPERTY_DATA.equals(propertyName)) {

            // list has changed, force reload of index
            setIndex(-1, ui.getIndex());

            // list has changed, fire empty property
            List list = (List) evt.getOldValue();
            fireEmpty(CollectionUtils.isEmpty(list));
        }
    }

    protected void fireEmpty(boolean wasEmpty) {
        ui.firePropertyChange(BeanComboBox.PROPERTY_EMPTY, wasEmpty,
                              isEmpty());
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy