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

io.ultreia.java4all.jaxx.widgets.combobox.JaxxComboBoxEditor Maven / Gradle / Ivy

package io.ultreia.java4all.jaxx.widgets.combobox;

/*-
 * #%L
 * JAXX :: Widgets
 * %%
 * Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
 * %%
 * 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%
 */

import io.ultreia.java4all.bean.JavaBean;
import io.ultreia.java4all.decoration.Decorator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.jaxx.runtime.swing.model.JaxxFilterableComboBoxModel;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ComboBoxEditor;
import javax.swing.InputMap;
import javax.swing.JComboBox;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
import java.awt.Component;
import java.awt.FocusTraversalPolicy;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Objects;

/**
 * Editor for the Combobox of the UI - uses the decorator
 */
public class JaxxComboBoxEditor implements ComboBoxEditor {

    private static final Logger log = LogManager.getLogger(JaxxComboBoxEditor.class);

    private final JaxxComboBox ui;
    private final String logPrefix;
    private final ComboBoxEditor wrapped;
    private final JaxxComboBoxModel model;
    private final JaxxFilterableComboBoxModel comboBoxModel;
    private final JaxxComboBoxConfig config;
    private final JComboBox combobox;

    static class DocumentKeyListener extends KeyAdapter {

        private final JComboBox combobox;
        private final JTextComponent editorComponent;
        private final JaxxComboBoxModel model;
        private final JaxxFilterableComboBoxModel comboBoxModel;

        private final boolean filterable;
        private final String logPrefix;
        Boolean doOpenPopup = null;

        DocumentKeyListener(JaxxComboBox ui, JTextComponent editorComponent) {
            this.combobox = ui.getCombobox();
            this.logPrefix = ui.getName() + " →";
            this.comboBoxModel = ui.getComboBoxModel();
            this.model = ui.getModel();
            this.editorComponent = editorComponent;
            this.filterable = ui.getConfig().isFilterable();
        }

        @Override
        public void keyTyped(KeyEvent e) {
            doOpenPopup = false;
            if (e.getKeyChar() == KeyEvent.VK_ENTER) {
                doOpenPopup = true;
            } else if (Character.isAlphabetic(e.getKeyChar()) || Character.isSpaceChar(e.getKeyChar())) {
                doOpenPopup = true;
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            if (KeyEvent.VK_ESCAPE == e.getKeyCode()) {
                log.debug(String.format("%s ESC , hide popup", logPrefix));
                e.consume();
                combobox.hidePopup();
                return;
            }
            if (KeyEvent.VK_ENTER == e.getKeyCode()) {
                if (combobox.isPopupVisible()) {
                    log.debug(String.format("%s Enter, hide popup ", logPrefix));
                    combobox.hidePopup();
                    e.consume();
                    return;
                }
            }
            if (KeyEvent.VK_TAB == e.getKeyCode()) {
                log.debug(String.format("%s Tab, consume event", logPrefix));
                return;
            }

            if (e.isActionKey() || e.isControlDown() || e.isShiftDown() || e.isAltDown() || e.isAltGraphDown()) {
                log.debug(String.format("%s Consume action key!!!!!", logPrefix));
                e.consume();
                return;
            }
            if (e.isConsumed()) {
                log.debug(String.format("%s Already consumed key!!!!!", logPrefix));
                return;
            }
            if (doOpenPopup != null && doOpenPopup && !combobox.isPopupVisible()) {
                log.debug(String.format("%s Will show popup, key event: %s", logPrefix, e.paramString()));
                combobox.showPopup();
                doOpenPopup = null;
            }

            // if the typed text does not match the selected item,
            // set the selected item to null

            O selectedItem = model.getSelectedItem();
            String text = editorComponent.getText();

            //FIXME Use BeanDecoratorAware
            String selectedItemString = comboBoxModel.decorateElement(selectedItem);
            if (selectedItem != null && !selectedItemString.equals(text)) {

                log.debug(String.format("%s Unselect previous selected item ('%s), since it does not match the text: '%s'", logPrefix, selectedItemString, text));

                selectedItem = model.selectedItem = null;
                model.mutateBeanProperty(null);

            }

            if (filterable && selectedItem == null) {

                log.debug(String.format("%s filterText: %s, keyEvent: '%s'", logPrefix, text, e.paramString()));

                String filterText = comboBoxModel.getFilterText();
                if (!Objects.equals(text, filterText)) {
                    log.info(String.format("%s Apply filter: '%s'", logPrefix, text));
                    updateFilter(logPrefix, combobox, comboBoxModel, editorComponent, text, text);
//                    // push back text (it was removed by UI after updating filtered elements
//                    editorComponent.setText(text);
                }
            }
        }
    }

    static  void updateFilter(String logPrefix, JComboBox combobox, JaxxFilterableComboBoxModel comboBoxModel, JTextComponent editorComponent, String text, String finalText) {

        // hide the popup before setting the filter, otherwise the popup height does not fit
        boolean wasPopupVisible = combobox.isShowing() && combobox.isPopupVisible();

        if (wasPopupVisible) {
            log.debug(String.format("%s hide popup before update filter '%s'", logPrefix, text));
            combobox.hidePopup();
        }
        log.debug(String.format("%s update filter '%s'", logPrefix, text));
        comboBoxModel.setFilterText(text);
        if (wasPopupVisible) {
            log.warn(String.format("%s show back popup after update filter '%s'", logPrefix, text));
            combobox.showPopup();
        }
        editorComponent.setText(finalText);

    }

    public JaxxComboBoxEditor(JaxxComboBox ui) {
        this.ui = Objects.requireNonNull(ui);
        this.logPrefix = ui.getName() + " →";
        this.combobox = ui.getCombobox();
        this.model = ui.getModel();
        this.comboBoxModel = ui.getComboBoxModel();
        this.config = model.getConfig();
        Decorator decorator = Objects.requireNonNull(config.getDecorator());
        this.wrapped = Objects.requireNonNull(ui.getCombobox().getEditor());
        JTextComponent editorComponent = getEditorComponent();
        InputMap inputMap = editorComponent.getInputMap();
        ActionMap actionMap = editorComponent.getActionMap();
        if (config.isTabToSelect()) {

            editorComponent.setFocusTraversalKeysEnabled(false);

            inputMap.put(KeyStroke.getKeyStroke("pressed TAB"), "focusNext");
            actionMap.put("focusNext", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    log.debug(String.format("%s → focusNext", ui.getName()));
                    autoSelect();
                    combobox.hidePopup();
                    FocusTraversalPolicy focusTraversalPolicy = ui.getFocusCycleRootAncestor().getFocusTraversalPolicy();
                    Component focusComponent = focusTraversalPolicy.getComponentAfter(ui.getFocusCycleRootAncestor(), combobox.getEditor().getEditorComponent());
                    changeFocus(focusComponent);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("shift pressed TAB"), "focusPrevious");
            actionMap.put("focusPrevious", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    log.debug(String.format("%s → focusPrevious", ui.getName()));
                    autoSelect();
                    combobox.hidePopup();
                    FocusTraversalPolicy focusTraversalPolicy = ui.getFocusCycleRootAncestor().getFocusTraversalPolicy();
                    Component focusComponent = focusTraversalPolicy.getComponentBefore(ui.getFocusCycleRootAncestor(), combobox.getEditor().getEditorComponent());
                    changeFocus(focusComponent);
                }
            });
        }

        if (config.isEnterToSelectUniqueUniverse()) {
            KeyStroke enterKeyStroke = KeyStroke.getKeyStroke("pressed ENTER");
            Object oldEnterKey = inputMap.get(enterKeyStroke);
            inputMap.put(enterKeyStroke, "enter");
            Action oldEnterAction = actionMap.get(oldEnterKey);
            actionMap.put("enter", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    log.debug(String.format("%s → enter pressed", ui.getName()));
                    if (autoSelect()) {
                        combobox.hidePopup();
                        return;
                    }
                    oldEnterAction.actionPerformed(e);
                }
            });
        }

        editorComponent.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                if (ui.isEnabled()) {
                    combobox.showPopup();
                }
            }

        });

        // if the typed text does not match the selected item,
        // set the selected item to null

        editorComponent.addKeyListener(new DocumentKeyListener<>(ui, editorComponent));
        if (!config.isBeanDecoratorAware()) {
            comboBoxModel.setDecorator(decorator);
            // init combobox renderer base on given decorator
//            combobox.setRenderer(new DecoratorListCellRenderer<>(decorator));
        }
        // Let's always act as in a cell (means selected value in popup is not selected for user, he must enter or click
        // to set new selected item)
        combobox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
        // FIXME: Check why we use also this to set selected item in model
        combobox.addItemListener(e -> {
            Object item1 = e.getItem();
            if (item1 instanceof String) {
                // Side effect of lost focus in cell editor with no real selected value
                return;
            }
            @SuppressWarnings("unchecked") O item = (O) e.getItem();
            if (e.getStateChange() == ItemEvent.SELECTED) {
                log.debug(String.format("itemStateChanged selected %s - %s", item, item != null ? item.getClass() : null));
                editorComponent.setForeground(null);
                model.setSelectedItem(item);

            } else {
                log.debug(String.format("itemStateChanged deselected %s - %s", item, item != null ? item.getClass() : null));
                editorComponent.setForeground(config.getInvalidComboEditorTextColor());
            }
        });

    }

    private void changeFocus(Component focusComponent) {
        if (focusComponent != null) {
            log.debug(String.format("%s → Change focus to %s", ui.getName(), focusComponent));
            SwingUtilities.invokeLater(focusComponent::requestFocusInWindow);
        }
    }

    private boolean autoSelect() {
        if (!combobox.isPopupVisible()) {
            // if not editing any longer, then selected nothing
            return false;
        }
        if (combobox.getItemCount() > 0) {
            int selectedIndex = ui.getHandler().getSelectedIndex();
            if (selectedIndex != -1) {
                log.debug(String.format("%s → Auto-select with *TAB* or *Enter* key", ui.getName()));
                combobox.setSelectedIndex(selectedIndex);
                return true;
            } else if (combobox.getItemCount() == 1) {
                log.debug(String.format("%s → Auto-select unique result with *TAB* or *Enter* key", ui.getName()));
                combobox.setSelectedIndex(0);
                return true;
            }
        }
        return false;
    }

    @Override
    public JTextComponent getEditorComponent() {
        return (JTextComponent) wrapped.getEditorComponent();
    }

    @Override
    public Object getItem() {
        return wrapped.getItem();
    }

    @Override
    public void setItem(Object anObject) {
        wrapped.setItem(anObject);
    }

    @Override
    public void selectAll() {
        wrapped.selectAll();
    }

    @Override
    public void addActionListener(ActionListener l) {
        wrapped.addActionListener(l);
    }

    @Override
    public void removeActionListener(ActionListener l) {
        wrapped.removeActionListener(l);
    }

    public void resetFilter(boolean force) {
        JTextComponent editorComponent = getEditorComponent();
        String text = editorComponent.getText();
        log.info(String.format("%s → Remove filter text, selected value was set from text '%s'", ui.getName(), text));
        updateFilter(logPrefix, combobox, comboBoxModel, editorComponent, "", force ? "" : text);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy