--- a/editor.completion/apichanges.xml Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/apichanges.xml Tue Sep 24 11:37:30 2013 +0200 @@ -108,6 +108,22 @@ + + + Addition of CompositeCompletionItem + + + + + +

+ CompositeCompletionItem interface was added to allow for + completion items containing possible sub-items. +

+
+ +
+ Addition of CompletionResultSet.setHasAdditionalItemsText() --- a/editor.completion/nbproject/project.properties Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/nbproject/project.properties Tue Sep 24 11:37:30 2013 +0200 @@ -44,4 +44,4 @@ javac.source=1.7 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=1.37.0 +spec.version.base=1.38.0 --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java Tue Sep 24 11:37:30 2013 +0200 @@ -637,7 +637,8 @@ } } // Call default action if ENTER was pressed - if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getID() == KeyEvent.KEY_PRESSED) { + if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getID() == KeyEvent.KEY_PRESSED + && (e.getModifiers() & InputEvent.ALT_MASK) == 0) { e.consume(); if (guardedPos) { Toolkit.getDefaultToolkit().beep(); @@ -1088,14 +1089,16 @@ pleaseWaitTimer.stop(); stopProfiling(); boolean hidePerformed = layout.hideCompletion(); - pleaseWaitDisplayed = false; - JTextComponent jtc = getActiveComponent(); - if (!completionOnly && hidePerformed && CompletionSettings.getInstance(jtc).documentationAutoPopup()) { - hideDocumentation(true); - } - if (jtc != null) { - jtc.putClientProperty("completion-visible", Boolean.FALSE); - jtc.putClientProperty("completion-active", Boolean.FALSE); + if (!layout.isCompletionVisible()) { + pleaseWaitDisplayed = false; + JTextComponent jtc = getActiveComponent(); + if (!completionOnly && hidePerformed && CompletionSettings.getInstance(jtc).documentationAutoPopup()) { + hideDocumentation(true); + } + if (jtc != null) { + jtc.putClientProperty("completion-visible", Boolean.FALSE); + jtc.putClientProperty("completion-active", Boolean.FALSE); + } } return hidePerformed; } @@ -1103,6 +1106,18 @@ /** * May be called from any thread but it will be rescheduled into AWT. */ + public void showCompletionSubItems() { + if (!SwingUtilities.isEventDispatchThread()) { + // Re-call this method in AWT if necessary + SwingUtilities.invokeLater(new ParamRunnable(ParamRunnable.SHOW_COMPLETION_SUB_ITEMS)); + return; + } + layout.showCompletionSubItems(); + } + + /** + * May be called from any thread but it will be rescheduled into AWT. + */ public void showDocumentation() { if (!SwingUtilities.isEventDispatchThread()) { // Re-call this method in AWT if necessary @@ -1588,11 +1603,12 @@ private final class ParamRunnable implements Runnable { private static final int SHOW_COMPLETION = 0; - private static final int SHOW_DOCUMENTATION = 1; - private static final int SHOW_TOOL_TIP = 2; - private static final int HIDE_COMPLETION_PANE = 3; - private static final int HIDE_DOCUMENTATION_PANE = 4; - private static final int HIDE_TOOL_TIP_PANE = 5; + private static final int SHOW_COMPLETION_SUB_ITEMS = 1; + private static final int SHOW_DOCUMENTATION = 2; + private static final int SHOW_TOOL_TIP = 3; + private static final int HIDE_COMPLETION_PANE = 4; + private static final int HIDE_DOCUMENTATION_PANE = 5; + private static final int HIDE_TOOL_TIP_PANE = 6; private final int opCode; private final boolean explicit; @@ -1620,6 +1636,10 @@ showCompletion(explicitQuery, false, delayQuery, type); break; + case SHOW_COMPLETION_SUB_ITEMS: + showCompletion(explicitQuery, false, delayQuery, type); + break; + case SHOW_DOCUMENTATION: showDocumentation(); break; --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionJList.java Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionJList.java Tue Sep 24 11:37:30 2013 +0200 @@ -56,7 +56,9 @@ import org.netbeans.editor.LocaleSupport; import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompositeCompletionItem; import org.netbeans.spi.editor.completion.LazyCompletionItem; +import org.openide.util.ImageUtilities; import org.openide.util.Utilities; /** @@ -67,11 +69,12 @@ public class CompletionJList extends JList { private static final int DARKER_COLOR_COMPONENT = 5; + private static final int SUB_MENU_ICON_GAP = 1; + private static final ImageIcon subMenuIcon = ImageUtilities.loadImageIcon("org/netbeans/modules/editor/hints/resources/suggestion.gif", false); // NOI18N; private final RenderComponent renderComponent; private Graphics cellPreferredSizeGraphics; - private int fixedItemHeight; private int maxVisibleRowCount; private JTextComponent editorComponent; @@ -90,8 +93,9 @@ renderComponent = new RenderComponent(); setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); setCellRenderer(new ListCellRenderer() { - private ListCellRenderer defaultRenderer = new DefaultListCellRenderer(); + private final ListCellRenderer defaultRenderer = new DefaultListCellRenderer(); + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if( value instanceof CompletionItem ) { CompletionItem item = (CompletionItem)value; @@ -315,10 +319,12 @@ this.data = data; } + @Override public int getSize() { return data.size(); } + @Override public Object getElementAt(int index) { return (index >= 0 && index < data.size()) ? data.get(index) : null; } @@ -364,6 +370,9 @@ // Render the item item.render(g, CompletionJList.this.getFont(), getForeground(), bgColor, itemRenderWidth, getHeight(), selected); + if (selected && item instanceof CompositeCompletionItem && !((CompositeCompletionItem)item).getSubItems().isEmpty()) { + g.drawImage(subMenuIcon.getImage(), itemRenderWidth - subMenuIcon.getIconWidth() - SUB_MENU_ICON_GAP, (height - subMenuIcon.getIconHeight()) / 2, null); + } if (separator) { g.setColor(Color.gray); @@ -383,7 +392,9 @@ return new Dimension(item.getPreferredWidth(cellPreferredSizeGraphics, CompletionJList.this.getFont()), fixedItemHeight); } - } + public static int arrowSpan() { + return SUB_MENU_ICON_GAP + subMenuIcon.getIconWidth() + SUB_MENU_ICON_GAP; + } } --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayout.java Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayout.java Tue Sep 24 11:37:30 2013 +0200 @@ -48,6 +48,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; +import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; @@ -56,8 +57,8 @@ import java.awt.event.MouseEvent; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.util.LinkedList; import java.util.List; -import java.util.Stack; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.swing.Action; @@ -75,6 +76,7 @@ import org.netbeans.editor.GuardedDocument; import org.netbeans.spi.editor.completion.CompletionDocumentation; import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompositeCompletionItem; import org.openide.text.CloneableEditorSupport; import org.openide.util.NbBundle; @@ -105,7 +107,7 @@ private final DocPopup docPopup; private final TipPopup tipPopup; - private Stack visiblePopups; + private LinkedList visiblePopups; CompletionLayout() { completionPopup = new CompletionPopup(); @@ -117,7 +119,7 @@ tipPopup = new TipPopup(); tipPopup.setLayout(this); tipPopup.setPreferDisplayAboveCaret(true); - visiblePopups = new Stack(); + visiblePopups = new LinkedList(); } public JTextComponent getEditorComponent() { @@ -132,9 +134,9 @@ } private void hideAll() { - completionPopup.hide(); - docPopup.hide(); - tipPopup.hide(); + for (CompletionLayoutPopup popup : visiblePopups) { + popup.hide(); + } visiblePopups.clear(); } @@ -145,15 +147,31 @@ visiblePopups.push(completionPopup); } + public void showCompletionSubItems() { + CompletionItem item = getSelectedCompletionItem(); + List subItems = item instanceof CompositeCompletionItem ? ((CompositeCompletionItem)item).getSubItems() : null; + if (subItems != null && !subItems.isEmpty()) { + Point p = getSelectedLocation(); + if (p != null) { + CompletionPopup popup = new CompletionPopup(); + popup.setLayout(this); + popup.show(subItems, p); + if (!visiblePopups.contains(popup)) + visiblePopups.push(popup); + } + } + } + public boolean hideCompletion() { - if (completionPopup.isVisible()) { - completionPopup.hide(); - completionPopup.completionScrollPane = null; - visiblePopups.remove(completionPopup); - return true; - } else { // not visible - return false; + for (CompletionLayoutPopup popup : visiblePopups) { + if (popup instanceof CompletionPopup && popup.isVisible()) { + popup.hide(); + ((CompletionPopup)popup).completionScrollPane = null; + visiblePopups.remove(popup); + return true; + } } + return false; } public boolean isCompletionVisible() { @@ -161,16 +179,34 @@ } public CompletionItem getSelectedCompletionItem() { - return completionPopup.getSelectedCompletionItem(); + for (CompletionLayoutPopup popup : visiblePopups) { + if (popup instanceof CompletionPopup && popup.isVisible()) { + return ((CompletionPopup)popup).getSelectedCompletionItem(); + } + } + return null; } public int getSelectedIndex() { - return completionPopup.getSelectedIndex(); + for (CompletionLayoutPopup popup : visiblePopups) { + if (popup instanceof CompletionPopup && popup.isVisible()) { + return ((CompletionPopup)popup).getSelectedIndex(); + } + } + return -1; + } + + public Point getSelectedLocation() { + for (CompletionLayoutPopup popup : visiblePopups) { + if (popup instanceof CompletionPopup && popup.isVisible()) { + return ((CompletionPopup)popup).getSelectedLocation(); + } + } + return null; } public void processKeyEvent(KeyEvent evt) { - for (int i = visiblePopups.size() - 1; i >= 0; i--) { - CompletionLayoutPopup popup = visiblePopups.get(i); + for (CompletionLayoutPopup popup : visiblePopups) { popup.processKeyEvent(evt); if (evt.isConsumed()) return; @@ -271,7 +307,6 @@ ) { updateLayout(docPopup); } - } else if (popup == docPopup) { // documentation popup if (isCompletionVisible()) { // Documentation must sync anchoring with completion @@ -294,6 +329,9 @@ // docPopup layout will be handled as part of completion popup layout updateLayout(completionPopup); } + } else { // completion sub-items popup + Rectangle occupiedBounds = popup.getAnchorOffsetBounds(); + popup.showAlongOrNextOccupiedBounds(occupiedBounds, occupiedBounds); } } @@ -313,8 +351,17 @@ private CompletionScrollPane completionScrollPane; + public void show(List data, Point location) { + show(data, null, -1, location, null, null, null, 0); + } + public void show(List data, String title, int anchorOffset, ListSelectionListener listSelectionListener, String additionalItemsText, String shortcutHint, int selectedIndex) { + show(data, title, anchorOffset, null, listSelectionListener, additionalItemsText, shortcutHint, selectedIndex); + } + + private void show(List data, String title, int anchorOffset, Point location, + ListSelectionListener listSelectionListener, String additionalItemsText, String shortcutHint, int selectedIndex) { JTextComponent editorComponent = getEditorComponent(); if (editorComponent == null) { @@ -338,6 +385,23 @@ public void mouseClicked(MouseEvent evt) { JTextComponent c = getEditorComponent(); if (SwingUtilities.isLeftMouseButton(evt)) { + if (completionScrollPane.getView().getSize().width - CompletionJList.arrowSpan() <= evt.getPoint().x) { + CompletionItem selectedItem = completionScrollPane.getSelectedCompletionItem(); + if (selectedItem instanceof CompositeCompletionItem && !((CompositeCompletionItem)selectedItem).getSubItems().isEmpty()) { + CompletionImpl.get().showCompletionSubItems(); + evt.consume(); + return; + } + } + for (CompletionLayoutPopup popup : getLayout().visiblePopups) { + if (popup instanceof CompletionPopup) { + if (popup == CompletionPopup.this) { + break; + } else { + popup.hide(); + } + } + } if (c != null && evt.getClickCount() == 2 ) { CompletionItem selectedItem = completionScrollPane.getSelectedCompletionItem(); @@ -382,7 +446,10 @@ // Set the new data getPreferredSize(); completionScrollPane.setData(data, title, selectedIndex); - setAnchorOffset(anchorOffset); + if (anchorOffset >= 0) + setAnchorOffset(anchorOffset); + if (location != null) + setLocation(location); Dimension prefSize = getPreferredSize(); @@ -412,6 +479,10 @@ return isVisible() ? completionScrollPane.getSelectedIndex() : -1; } + public Point getSelectedLocation() { + return isVisible() ? completionScrollPane.getSelectedLocation() : null; + } + public void processKeyEvent(KeyEvent evt) { if (isVisible()) { Object actionMapKey = completionScrollPane.getInputMap().get( @@ -431,7 +502,6 @@ protected int getAnchorHorizontalShift() { return COMPLETION_ANCHOR_HORIZONTAL_SHIFT; } - } private static final class DocPopup extends CompletionLayoutPopup { @@ -484,7 +554,6 @@ protected int getAnchorHorizontalShift() { return COMPLETION_ANCHOR_HORIZONTAL_SHIFT; } - } private static final class TipPopup extends CompletionLayoutPopup { @@ -514,8 +583,6 @@ CompletionImpl.get().hideToolTip(); } } - } - - } - + } + } } --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayoutPopup.java Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayoutPopup.java Tue Sep 24 11:37:30 2013 +0200 @@ -136,6 +136,11 @@ anchorOffsetBounds = null; } + final void setLocation(Point location) { + this.anchorOffset = -1; + anchorOffsetBounds = new Rectangle(location); + } + final int getAnchorOffset() { int offset = anchorOffset; if (offset == -1) { --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionScrollPane.java Tue Sep 24 00:36:35 2013 +0000 +++ a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionScrollPane.java Tue Sep 24 11:37:30 2013 +0200 @@ -47,6 +47,7 @@ import java.awt.Color; import java.awt.Dimension; +import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -60,11 +61,10 @@ import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionListener; -import javax.swing.plaf.TextUI; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; -import javax.swing.text.EditorKit; import org.netbeans.editor.BaseKit; import org.netbeans.editor.ext.ExtKit; import org.netbeans.spi.editor.completion.CompletionItem; @@ -86,7 +86,8 @@ private static final String COMPLETION_PGDN = "completion-pgdn"; //NOI18N private static final String COMPLETION_BEGIN = "completion-begin"; //NOI18N private static final String COMPLETION_END = "completion-end"; //NOI18N - + private static final String COMPLETION_SUBITEMS_SHOW = "completion-subitems-show"; //NOI18N + private static final int ACTION_ESCAPE = 0; private static final int ACTION_COMPLETION_UP = 1; private static final int ACTION_COMPLETION_DOWN = 2; @@ -94,8 +95,9 @@ private static final int ACTION_COMPLETION_PGDN = 4; private static final int ACTION_COMPLETION_BEGIN = 5; private static final int ACTION_COMPLETION_END = 6; - - private CompletionJList view; + private static final int ACTION_COMPLETION_SUBITEMS_SHOW = 7; + + private final CompletionJList view; private List dataObj; @@ -148,6 +150,14 @@ return view.getSelectedIndex(); } + public Point getSelectedLocation() { + Rectangle r = view.getCellBounds(getSelectedIndex(), getSelectedIndex()); + Point p = new Point(r.getLocation()); + SwingUtilities.convertPointToScreen(p, view); + p.x += r.width; + return p; + } + public @Override Dimension getPreferredSize() { Dimension prefSize = super.getPreferredSize(); Dimension labelSize = topLabel != null ? topLabel.getPreferredSize() : new Dimension(0, 0); @@ -162,6 +172,10 @@ return prefSize; } + protected CompletionJList getView() { + return view; + } + private void setTitle(String title) { if (title == null) { if (topLabel != null) { @@ -185,7 +199,7 @@ // This method is implemented due to the issue // #25715 - Attempt to search keymap for the keybinding that logically corresponds to the action KeyStroke[] ret = new KeyStroke[] { defaultKey }; - if (component != null) { + if (component != null && editorActionName != null) { Action a = component.getActionMap().get(editorActionName); Keymap km = component.getKeymap(); if (a != null && km != null) { @@ -200,8 +214,8 @@ private void registerKeybinding(int action, String actionName, KeyStroke stroke, String editorActionName, JTextComponent component){ KeyStroke[] keys = findEditorKeys(editorActionName, stroke, component); - for (int i = 0; i < keys.length; i++) { - getInputMap().put(keys[i], actionName); + for (KeyStroke key : keys) { + getInputMap().put(key, actionName); } getActionMap().put(actionName, new CompletionPaneAction(action)); } @@ -241,6 +255,10 @@ registerKeybinding(ACTION_COMPLETION_END, COMPLETION_END, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), BaseKit.endLineAction, component); + + registerKeybinding(ACTION_COMPLETION_SUBITEMS_SHOW, COMPLETION_SUBITEMS_SHOW, + KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_MASK), + null, component); } List testGetData() { @@ -248,19 +266,21 @@ } private class CompletionPaneAction extends AbstractAction { - private int action; + private final int action; private CompletionPaneAction(int action) { this.action = action; } + @Override public void actionPerformed(ActionEvent actionEvent) { switch (action) { - case ACTION_ESCAPE: - LogRecord r = new LogRecord(Level.FINE, "COMPL_CANCEL"); // NOI18N - CompletionImpl.uilog(r); - CompletionImpl.get().hideCompletion(false); - break; + case ACTION_ESCAPE: + if (CompletionImpl.get().hideCompletion(false)) { + LogRecord r = new LogRecord(Level.FINE, "COMPL_CANCEL"); // NOI18N + CompletionImpl.uilog(r); + } + break; case ACTION_COMPLETION_UP: view.up(); break; @@ -271,13 +291,16 @@ view.pageUp(); break; case ACTION_COMPLETION_PGDN: - view.pageDown(); + view.pageDown(); break; case ACTION_COMPLETION_BEGIN: - view.begin(); + view.begin(); break; case ACTION_COMPLETION_END: - view.end(); + view.end(); + break; + case ACTION_COMPLETION_SUBITEMS_SHOW: + CompletionImpl.get().showCompletionSubItems(); break; } } --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ ddba7068906e Tue Sep 24 11:37:30 2013 +0200 @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.editor.completion; + +import java.util.List; + +/** + * The interface representing a completion item containing possible sub-items. + * + * @author Dusan Balek + * @since 1.38 + */ +public interface CompositeCompletionItem extends CompletionItem { + + /** + * Gets sub-items of the code completion item. The sub-items may be shown + * as a sub-menu for the given item. + * + * @return non-null list of sub-items. + */ + List getSubItems(); +}