org.apache.cayenne.modeler.util.PathChooserComboBoxCellEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cayenne.modeler.util.PathChooserComboBoxCellEditor.java

Source

/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 ****************************************************************/

package org.apache.cayenne.modeler.util;

import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.util.combo.AutoCompletion;
import org.apache.commons.lang.StringUtils;

import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ListCellRenderer;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.table.TableCellEditor;
import javax.swing.text.JTextComponent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * This class used as cell editor, when you need to
 * choose path in comboBox and use autocompletion.
 */
@SuppressWarnings("WeakerAccess")
public abstract class PathChooserComboBoxCellEditor extends AbstractCellEditor
        implements TableCellEditor, ActionListener, PopupMenuListener {

    protected JComboBox<String> comboBoxPathChooser;
    protected int previousEmbeddedLevel = 0;
    protected EntityTreeModel treeModel;
    protected int row;
    private JTable table;

    protected abstract void enterPressed(JTable table);

    protected abstract EntityTreeModel createTreeModelForComboBox(int indexInTable);

    protected abstract Object getCurrentNodeToInitializeCombo(CayenneTableModel model, int row);

    protected abstract String getPathToInitializeCombo(CayenneTableModel model, int row);

    protected void initializeCombo(CayenneTableModel model, int row, final JTable table) {
        Object currentNode = getCurrentNodeToInitializeCombo(model, row);
        String dbAttributePath = getPathToInitializeCombo(model, row);
        List<String> nodeChildren = getChildren(currentNode, dbAttributePath);
        this.table = table;
        comboBoxPathChooser = Application.getWidgetFactory().createComboBox(nodeChildren, false);
        comboBoxPathChooser.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent event) {
                if (event.getKeyCode() == KeyEvent.VK_ENTER) {
                    enterPressed(table);
                    return;
                }
                parsePathString(event.getKeyChar());
            }
        });
        AutoCompletion.enable(comboBoxPathChooser, true, true);
        ((JComponent) comboBoxPathChooser.getEditor().getEditorComponent()).setBorder(null);
        comboBoxPathChooser.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
        comboBoxPathChooser.setRenderer(new PathChooserComboBoxCellRenderer());
        comboBoxPathChooser.addActionListener(this);
        comboBoxPathChooser.addPopupMenuListener(this);
    }

    private void setComboModelAccordingToPath(String pathString) {
        List<String> currentNodeChildren = new ArrayList<>();
        currentNodeChildren.addAll(getChildren(getCurrentNode(pathString), pathString));
        comboBoxPathChooser.setModel(new DefaultComboBoxModel<>(currentNodeChildren.toArray(new String[0])));
        comboBoxPathChooser.setSelectedItem(pathString);
        if (!pathString.isEmpty()) {
            comboBoxPathChooser.showPopup();
        }
    }

    protected void parsePathString(char lastEnteredCharacter) {
        JTextComponent editorComponent = (JTextComponent) (comboBoxPathChooser).getEditor().getEditorComponent();
        String pathString = editorComponent.getText();
        if (pathString != null && pathString.isEmpty()) {
            setComboModelAccordingToPath("");
            previousEmbeddedLevel = 0;
            return;
        }

        if (lastEnteredCharacter == '.') {
            processDotEntered();
            previousEmbeddedLevel = StringUtils.countMatches(pathString, ".");
            return;
        }

        int currentEmbeddedLevel = StringUtils.countMatches(pathString, ".");
        if (previousEmbeddedLevel != currentEmbeddedLevel) {
            previousEmbeddedLevel = currentEmbeddedLevel;
            String[] pathStrings = pathString.split(Pattern.quote("."));
            String lastStringInPath = pathStrings[pathStrings.length - 1];
            String saveDbAttributePath = pathString;
            pathString = pathString.replaceAll(lastStringInPath + "$", "");
            List<String> currentNodeChildren = new ArrayList<>();
            currentNodeChildren.addAll(getChildren(getCurrentNode(pathString), pathString));
            comboBoxPathChooser.setModel(new DefaultComboBoxModel<>(currentNodeChildren.toArray(new String[0])));
            comboBoxPathChooser.setSelectedItem(saveDbAttributePath);
        }
    }

    protected void processDotEntered() {
        JTextComponent editorComponent = (JTextComponent) (comboBoxPathChooser).getEditor().getEditorComponent();

        String dbAttributePath = editorComponent.getText();
        if (".".equals(dbAttributePath)) {
            setComboModelAccordingToPath("");
            return;
        }
        char secondFromEndCharacter = dbAttributePath.charAt(dbAttributePath.length() - 2);
        if (secondFromEndCharacter == '.') {
            // two dots entered one by one , we replace it by one dot
            editorComponent.setText(dbAttributePath.substring(0, dbAttributePath.length() - 1));
            return;
        }
        String[] pathStrings = dbAttributePath.split(Pattern.quote("."));
        String lastStringInPath = pathStrings[pathStrings.length - 1];

        //we will check if lastStringInPath is correct name of DbAttribute or DbRelationship
        //for appropriate previous node in path. if it is not we won't add entered dot to dbAttributePath
        String dbAttributePathForPreviousNode;
        if (pathStrings.length == 1) {
            //previous root is treeModel.getRoot()
            dbAttributePathForPreviousNode = "";
        } else {
            dbAttributePathForPreviousNode = dbAttributePath.replaceAll('.' + lastStringInPath + ".$", "");
        }
        List<String> potentialVariantsToChoose = getChildren(getCurrentNode(dbAttributePathForPreviousNode), "");
        if (potentialVariantsToChoose.contains(lastStringInPath)
                && !(getCurrentNode(dbAttributePath) instanceof DbAttribute)) {
            setComboModelAccordingToPath(dbAttributePath);
        } else {
            editorComponent.setText(dbAttributePath.substring(0, dbAttributePath.length() - 1));
        }
    }

    /**
     * find current node by path
     *
     * @param pathString db path
     * @return last node in path which matches DbRelationship or DbAttribute
     */
    protected Object getCurrentNode(String pathString) {
        //case for new attribute
        if (pathString == null || pathString.isEmpty()) {
            return treeModel.getRoot();
        }
        String[] pathStrings = pathString.split(Pattern.quote("."));
        Object root = treeModel.getRoot();
        for (String rootChildText : pathStrings) {
            for (int j = 0; j < treeModel.getChildCount(root); j++) {
                Object child = treeModel.getChild(root, j);
                String objectName = ModelerUtil.getObjectName(child);
                if (objectName.equals(rootChildText)) {
                    root = child;
                    break;
                }
            }
        }
        return root;
    }

    /**
     * @param node       for which we will find children
     * @param pathString string which will be added to each child to make right autocomplete
     * @return list with children , which will be used to autocomplete
     */
    protected List<String> getChildren(Object node, String pathString) {
        List<String> currentNodeChildren = new ArrayList<>();
        for (int j = 0; j < treeModel.getChildCount(node); j++) {
            Object child = treeModel.getChild(node, j);
            String relationshipName = ModelerUtil.getObjectName(child);
            currentNodeChildren.add(pathString + relationshipName);
        }
        return currentNodeChildren;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        //for some reason comboBoxPathChooser don't load selected item text, so we made it by hand
        if (comboBoxPathChooser.getSelectedIndex() != (-1)) {
            ((JTextComponent) (comboBoxPathChooser).getEditor().getEditorComponent())
                    .setText(comboBoxPathChooser.getSelectedItem().toString());
        }
    }

    @Override
    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        if (comboBoxPathChooser.getSelectedIndex() != -1
                && !((JTextComponent) (comboBoxPathChooser).getEditor().getEditorComponent()).getText().isEmpty()) {
            enterPressed(table);
        }
    }

    @Override
    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    }

    @Override
    public void popupMenuCanceled(PopupMenuEvent e) {
    }

    private final class PathChooserComboBoxCellRenderer extends DefaultListCellRenderer {

        private final ImageIcon rightArrow = ModelerUtil.buildIcon("scroll_right.gif");

        @SuppressWarnings("unchecked")
        @Override
        public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {

            JPanel panel = new JPanel(new BorderLayout());
            JLabel label = new JLabel((String) value);
            label.setFont(new Font("Verdana", Font.PLAIN, 12));
            panel.add(label);

            Object currentNode = getCurrentNode((String) value);
            if (treeModel.isLeaf(currentNode)) {
                ListCellRenderer leafRenderer = CellRenderers.listRenderer();
                return leafRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            } else {
                DefaultListCellRenderer nonLeafTextRenderer = new DefaultListCellRenderer();
                Component text = nonLeafTextRenderer.getListCellRendererComponent(list, value, index, isSelected,
                        cellHasFocus);
                panel.setBackground(text.getBackground());
                panel.add(new JLabel(rightArrow), BorderLayout.EAST);
                return panel;
            }
        }
    }
}