http://git-wip-us.apache.org/repos/asf/cayenne/blob/b444b1df/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/PathChooserComboBoxCellEditor.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/PathChooserComboBoxCellEditor.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/PathChooserComboBoxCellEditor.java new file mode 100644 index 0000000..ae4a639 --- /dev/null +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/PathChooserComboBoxCellEditor.java @@ -0,0 +1,192 @@ +/***************************************************************** + * 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.modeler.Application; +import org.apache.cayenne.modeler.util.combo.AutoCompletion; +import org.apache.commons.lang.StringUtils; + +import javax.swing.AbstractCellEditor; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JTable; +import javax.swing.table.TableCellEditor; +import javax.swing.text.JTextComponent; +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. + */ +public abstract class PathChooserComboBoxCellEditor extends AbstractCellEditor implements TableCellEditor { + + protected JComboBox comboBoxPathChooser; + protected int previousEmbeddedLevel = 0; + protected EntityTreeModel treeModel; + protected int row; + + 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); + 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, false, true); + } + + private void setComboModelAccordingToPath(String pathString) { + List<String> currentNodeChildren = new ArrayList<>(); + currentNodeChildren.add(pathString); + currentNodeChildren.addAll(getChildren(getCurrentNode(pathString), pathString)); + comboBoxPathChooser.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + comboBoxPathChooser.showPopup(); + comboBoxPathChooser.setPopupVisible(true); + } + + protected void parsePathString(char lastEnteredCharacter) { + JTextComponent editorComponent = (JTextComponent) (comboBoxPathChooser).getEditor().getEditorComponent(); + + String pathString = editorComponent.getText(); + if (pathString != null && pathString.isEmpty()) { + setComboModelAccordingToPath(""); + previousEmbeddedLevel = StringUtils.countMatches(pathString, "."); + return; + } + + if (lastEnteredCharacter == '.') { + processDotEntered(); + previousEmbeddedLevel = StringUtils.countMatches(pathString, "."); + return; + } + + int currentEmbeddedLevel = StringUtils.countMatches(pathString, "."); + if (previousEmbeddedLevel != currentEmbeddedLevel) { + previousEmbeddedLevel = currentEmbeddedLevel; + List<String> currentNodeChildren = new ArrayList<>(); + String[] pathStrings = pathString.split(Pattern.quote(".")); + String lastStringInPath = pathStrings[pathStrings.length - 1]; + String saveDbAttributePath = pathString; + pathString = pathString.replaceAll(lastStringInPath + "$", ""); + currentNodeChildren.addAll(getChildren(getCurrentNode(pathString), pathString)); + comboBoxPathChooser.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + editorComponent.setText(saveDbAttributePath); + return; + } + } + + private 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.replace('.' + lastStringInPath, ""); + } + List<String> potentialVariantsToChoose = getChildren(getCurrentNode(dbAttributePathForPreviousNode), ""); + if (potentialVariantsToChoose.contains(lastStringInPath)) { + setComboModelAccordingToPath(dbAttributePath); + } else { + editorComponent.setText(dbAttributePath.substring(0, dbAttributePath.length() - 1)); + } + previousEmbeddedLevel = StringUtils.countMatches(dbAttributePath, "."); + } + + /** + * find current node by dbAttributePath + * + * @param pathString + * @return last node in dbAttributePath 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; + } +}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/b444b1df/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ProjectUtil.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ProjectUtil.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ProjectUtil.java index 20aaf3d..fc7735d 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ProjectUtil.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ProjectUtil.java @@ -19,11 +19,6 @@ package org.apache.cayenne.modeler.util; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - import org.apache.cayenne.configuration.DataChannelDescriptor; import org.apache.cayenne.configuration.DataNodeDescriptor; import org.apache.cayenne.map.Attribute; @@ -48,6 +43,11 @@ import org.apache.cayenne.query.EJBQLQuery; import org.apache.cayenne.query.Query; import org.apache.cayenne.util.Util; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + /** * Provides utility methods to perform various manipulations with project objects. */ @@ -198,7 +198,7 @@ public class ProjectUtil { } } - + /** * Changes the name of the embeddable attribute and all references to this embeddable attribute. */ @@ -207,7 +207,7 @@ public class ProjectUtil { attribute.setName(newName); Embeddable embeddable = attribute.getEmbeddable(); - + if (embeddable != null) { embeddable.removeAttribute(oldName); embeddable.addAttribute(attribute); @@ -245,46 +245,15 @@ public class ProjectUtil { for (ObjAttribute att : entity.getAttributes()) { // If flattenet atribute - if (att.getDbAttributePath() != null - && att.getDbAttributePath().contains(".")) { - String[] pathSplit = att.getDbAttributePath().split("\\."); + String dbAttributePath = att.getDbAttributePath(); + if (dbAttributePath != null + && dbAttributePath.contains(".")) { + String[] pathSplit = dbAttributePath.split("\\."); // If flattened attribute if (pathSplit.length > 1) { - DbEntity currentEnt = dbEnt; - StringBuilder pathBuf = new StringBuilder(); - boolean isTruePath = true; - - if (currentEnt != null) { - - for (int j = 0; j < pathSplit.length; j++) { - - if (j == pathSplit.length - 1 && isTruePath) { - DbAttribute dbAttribute = (DbAttribute) currentEnt - .getAttribute(pathSplit[j]); - if (dbAttribute != null) { - pathBuf.append(dbAttribute.getName()); - } - else { - isTruePath = false; - } - } - else if (isTruePath) { - DbRelationship dbRelationship = (DbRelationship) currentEnt - .getRelationship(pathSplit[j]); - if (dbRelationship != null) { - currentEnt = (DbEntity) dbRelationship - .getTargetEntity(); - pathBuf.append(dbRelationship.getName()); - pathBuf.append("."); - } - else { - isTruePath = false; - } - } - } - } + boolean isTruePath = isDbAttributePathCorrect(dbEnt, dbAttributePath); if (!isTruePath) { att.setDbAttributePath(null); @@ -319,6 +288,34 @@ public class ProjectUtil { } /** + * check if path is correct. path is correct when he consist from <code>DbRelationship</code> + * objects, each <code>DbRelationship</code> object have following <code>DbRelationship</code> + * object as a target, last component is <code>DbAttribute</code> + * + * @param currentEnt current db entity + * @param dbAttributePath path to check + * @return if path is correct return true + */ + public static boolean isDbAttributePathCorrect(DbEntity currentEnt, String dbAttributePath) { + if (currentEnt == null) { + return true; + } + + String[] pathSplit = dbAttributePath.split("\\."); + + int size = pathSplit.length - 1; + for (int j = 0; j < size; j++) { + DbRelationship relationship = currentEnt.getRelationship(pathSplit[j]); + if (relationship == null) { + return false; + } + currentEnt = relationship.getTargetEntity(); + } + + return currentEnt.getAttribute(pathSplit[(size)]) != null; + } + + /** * Clears all the mapping between this obj entity and its current db entity. Clears * mapping between entities, attributes and relationships. */ @@ -439,4 +436,4 @@ public class ProjectUtil { } return relationships; } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cayenne/blob/b444b1df/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/SortButtonRenderer.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/SortButtonRenderer.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/SortButtonRenderer.java index 524d8ba..595ba4d 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/SortButtonRenderer.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/SortButtonRenderer.java @@ -18,51 +18,33 @@ ****************************************************************/ package org.apache.cayenne.modeler.util; -import java.awt.Color; -import java.awt.Component; -import java.awt.Insets; -import java.util.Hashtable; - import javax.swing.BorderFactory; -import javax.swing.JButton; +import javax.swing.JLabel; import javax.swing.JTable; -import javax.swing.border.MatteBorder; +import javax.swing.border.Border; import javax.swing.table.TableCellRenderer; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.util.Hashtable; -public class SortButtonRenderer extends JButton implements TableCellRenderer { +public class SortButtonRenderer extends JLabel implements TableCellRenderer { public static final int NONE = 0; public static final int DOWN = 1; public static final int UP = 2; - private int pushedColumn; private Hashtable state; - private JButton downButton, upButton; + private JLabel downLabel , upLabel; public SortButtonRenderer() { - MatteBorder matteBorder = BorderFactory.createMatteBorder(0, 0, 1, 1, Color.gray); - setBorder(matteBorder); - - pushedColumn = -1; state = new Hashtable(); - setMargin(new Insets(0, 0, 0, 0)); - setHorizontalTextPosition(CENTER); - setIcon(new BlankIcon()); + downLabel = new JLabel(); + downLabel.setIcon(new BevelArrowIcon(BevelArrowIcon.DOWN, false, false)); - downButton = new JButton(); - - downButton.setBorder(matteBorder); - downButton.setMargin(new Insets(0, 0, 0, 0)); - downButton.setHorizontalTextPosition(LEFT); - downButton.setIcon(new BevelArrowIcon(BevelArrowIcon.DOWN, false, false)); - downButton.setPressedIcon(new BevelArrowIcon(BevelArrowIcon.DOWN, false, true)); - - upButton = new JButton(); - upButton.setBorder(matteBorder); - upButton.setMargin(new Insets(0, 0, 0, 0)); - upButton.setHorizontalTextPosition(LEFT); - upButton.setIcon(new BevelArrowIcon(BevelArrowIcon.UP, false, false)); + upLabel = new JLabel(); + upLabel.setIcon(new BevelArrowIcon(BevelArrowIcon.UP, false, false)); } public Component getTableCellRendererComponent( @@ -72,49 +54,51 @@ public class SortButtonRenderer extends JButton implements TableCellRenderer { boolean hasFocus, int row, int column) { - JButton button = this; - Object obj = state.get(new Integer(column)); + JLabel label = this; + Object obj = state.get(column); if (obj != null) { - if (((Integer) obj).intValue() == DOWN) { - button = downButton; + if ((Integer) obj == DOWN) { + label = downLabel; } else { - button = upButton; + label = upLabel; } } - button.setText((value == null) ? "" : value.toString()); - return button; - } - - public void setPressedColumn(int col) { - pushedColumn = col; + Border border = BorderFactory.createLineBorder(Color.GRAY, 1); + label.setText(' ' + ((value == null) ? "" : value.toString())); + label.setFont(new Font("Verdana", Font.BOLD , 13)); + label.setHorizontalTextPosition(JLabel.LEFT); + label.setBorder(border); + label.setOpaque(true); + label.setBackground(new Color(204, 238, 255)); + return label; } public void setSelectedColumn(int col, boolean isAscOrder) { - if (col < 0) + if (col < 0) { return; + } Integer value = null; //shows the direction of ordering if (isAscOrder) { - value = new Integer(DOWN); - } - else { - value = new Integer(UP); + value = DOWN; + } else { + value = UP; } state.clear(); - state.put(new Integer(col), value); + state.put(col, value); } public int getState(int col) { int retValue; - Object obj = state.get(new Integer(col)); + Object obj = state.get(col); if (obj == null) { retValue = NONE; } else { - if (((Integer) obj).intValue() == DOWN) { + if ((Integer) obj == DOWN) { retValue = DOWN; } else { http://git-wip-us.apache.org/repos/asf/cayenne/blob/b444b1df/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TableHeaderListener.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TableHeaderListener.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TableHeaderListener.java index 29bf1a7..b665730 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TableHeaderListener.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TableHeaderListener.java @@ -72,14 +72,12 @@ public class TableHeaderListener extends MouseAdapter { } public void mouseReleased(MouseEvent e) { - renderer.setPressedColumn(-1); // clear header.repaint(); } public void sortByDefinedColumn(int col, int sortCol, boolean order) { CayenneTableModel model = (CayenneTableModel) table.getModel(); if (model.isColumnSortable(sortCol)) { - renderer.setPressedColumn(col); renderer.setSelectedColumn(col, order); header.repaint(); http://git-wip-us.apache.org/repos/asf/cayenne/blob/b444b1df/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/combo/AutoCompletion.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/combo/AutoCompletion.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/combo/AutoCompletion.java index 2e86251..9c8a371 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/combo/AutoCompletion.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/combo/AutoCompletion.java @@ -19,15 +19,17 @@ package org.apache.cayenne.modeler.util.combo; +import javax.swing.JComboBox; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.text.JTextComponent; +import java.awt.Component; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; -import javax.swing.JComboBox; -import javax.swing.SwingUtilities; -import javax.swing.text.JTextComponent; - /** * AutoCompletion class handles user input and suggests matching variants (see CAY-911) * @@ -55,8 +57,8 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { protected AutoCompletion(final JComboBox comboBox, boolean strict, boolean allowsUserValues) { this.comboBox = comboBox; textEditor = ((JTextComponent)comboBox.getEditor().getEditorComponent()); - - this.allowsUserValues = allowsUserValues; + + this.allowsUserValues = allowsUserValues; suggestionList = new SuggestionList(comboBox, strict); @@ -75,13 +77,17 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { */ public static void enable(JComboBox comboBox, boolean strict, boolean allowsUserValues) { comboBox.setEditable(true); - + KeyListener[] listeners = comboBox.getEditor().getEditorComponent().getListeners(KeyListener.class); comboBox.setEditor(new CustomTypeComboBoxEditor(comboBox, allowsUserValues)); - + for (KeyListener listener : listeners) { + comboBox.getEditor().getEditorComponent().addKeyListener(listener); + } + + AutoCompletion ac = new AutoCompletion(comboBox, strict, allowsUserValues); comboBox.addFocusListener(ac); ac.textEditor.addKeyListener(ac); - + //original keys would not work properly SwingUtilities.replaceUIActionMap(comboBox, null); } @@ -100,23 +106,34 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { } public void keyPressed(KeyEvent e) { - handleKeyPressed(comboBox, e); + handleKeyPressed(e); } - public void keyReleased(KeyEvent e) {} + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE || + e.getKeyCode() == KeyEvent.VK_ENTER) { + + String text = textEditor.getText(); + if (comboBox.isShowing()) { + suggestionList.hide(); + suggestionList.filter(text); + suggestionList.show(); + } + } + } public void keyTyped(KeyEvent e) {} - + public void run() { String text = textEditor.getText(); - + //need to hide first because Swing incorrectly updates popups (getSize() returns //dimension not the same as seen on the screen) suggestionList.hide(); - + if (comboBox.isShowing()) { suggestionList.filter(text); - + if (suggestionList.getItemCount() > 0) { suggestionList.show(); } @@ -127,24 +144,31 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { * Calculates next selection row, according to a pressed key and selects it. * This might affect either suggestion list or original popup */ - private void handleKeyPressed(JComboBox comboBox, KeyEvent e) { + private void handleKeyPressed(KeyEvent e) { boolean suggest = suggestionList.isVisible(); - - int sel, next, max; - + if (suggest) { - sel = suggestionList.getSelectedIndex(); - max = suggestionList.getItemCount() - 1; + processKeyPressedWhenSuggestionListIsVisible(e); } else { - sel = comboBox.getSelectedIndex(); - max = comboBox.getItemCount() - 1; + processKeyPressedWhenSuggestionListIsInvisible(e); } - + + //scroll doesn't work in suggestionList..so we will scroll manually + suggestionListScrolling(); + + textEditor.requestFocus(); + } + + private void processKeyPressedWhenSuggestionListIsInvisible(KeyEvent e){ + int sel = comboBox.getSelectedIndex(); + int max = comboBox.getItemCount() - 1; + + int next; switch (e.getKeyCode()) { case KeyEvent.VK_UP: case KeyEvent.VK_NUMPAD8: - next = sel - 1; + next = sel - 1; break; case KeyEvent.VK_DOWN: case KeyEvent.VK_NUMPAD2: @@ -163,42 +187,71 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { next = max; break; case KeyEvent.VK_ENTER: - if (suggest) { - Object value = suggestionList.getSelectedValue(); - if (!allowsUserValues && value == null && suggestionList.getItemCount() > 0) { - value = suggestionList.getItemAt(0); - } - - //reset the item (value == null) only if user values are not supported - if (value != null || !allowsUserValues) { - comboBox.setSelectedItem(value); - } - suggestionList.hide(); - } return; - case KeyEvent.VK_ESCAPE: - if (suggest) { - suggestionList.hide(); - } return; - - case KeyEvent.VK_CONTROL: - case KeyEvent.VK_ALT: - case KeyEvent.VK_SHIFT: - return; - default: //invoke in end of AWT thread so that information in textEditor would update SwingUtilities.invokeLater(this); return; } - - /** - * Handle navigation keys - */ + e.consume(); + handleNavigationKeys(false,next,sel,max); + } + private void processKeyPressedWhenSuggestionListIsVisible(KeyEvent e){ + int sel = suggestionList.getSelectedIndex(); + int max = suggestionList.getItemCount() - 1; + int next; + switch (e.getKeyCode()) { + case KeyEvent.VK_UP: + case KeyEvent.VK_NUMPAD8: + next = sel - 1; + break; + case KeyEvent.VK_DOWN: + case KeyEvent.VK_NUMPAD2: + next = sel + 1; + break; + case KeyEvent.VK_PAGE_UP: + next = sel - comboBox.getMaximumRowCount(); + break; + case KeyEvent.VK_PAGE_DOWN: + next = sel + comboBox.getMaximumRowCount(); + break; + case KeyEvent.VK_HOME: + next = 0; + break; + case KeyEvent.VK_END: + next = max; + break; + case KeyEvent.VK_ENTER: + processEnterPressed(); + return; + case KeyEvent.VK_ESCAPE: + suggestionList.hide(); + return; + default: + //invoke in end of AWT thread so that information in textEditor would update + SwingUtilities.invokeLater(this); + return; + } e.consume(); + handleNavigationKeys(true,next,sel,max); + } + + private void processEnterPressed(){ + Object value = suggestionList.getSelectedValue(); + if (!allowsUserValues && value == null && suggestionList.getItemCount() > 0) { + value = suggestionList.getItemAt(0); + } + //reset the item (value == null) only if user values are not supported + if (value != null || !allowsUserValues) { + comboBox.setSelectedItem(value); + } + suggestionList.hide(); + } + + private void handleNavigationKeys(boolean suggest, int next, int sel, int max){ if (!suggest && !comboBox.isPopupVisible()) { comboBox.setPopupVisible(true); return; @@ -208,22 +261,33 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { if (next < 0) { next = 0; } - + if (next > max) { next = max; } - + if (next != sel) { if (suggest) { suggestionList.setSelectedIndex(next); - } - else { - comboBox.setPopupVisible(true); - comboBox.setSelectedIndex(next); + } else { + comboBox.setPopupVisible(true); + comboBox.setSelectedIndex(next); } } - - textEditor.requestFocus(); + } + } + + private void suggestionListScrolling(){ + Component c = suggestionList.getComponent(0); + if (c instanceof JScrollPane) { + double height = suggestionList.getPreferredSize().getHeight(); + int itemCount = suggestionList.getItemCount(); + int selectedIndex = suggestionList.getSelectedIndex(); + double scrollValue = Math.ceil(height*selectedIndex/itemCount); + JScrollPane scrollPane = (JScrollPane) c; + JScrollBar scrollBar = scrollPane.getVerticalScrollBar(); + scrollBar.setValue((int) scrollValue); } } } +