diff --git a/jumpto/src/org/netbeans/modules/jumpto/type/GoToPanel.java b/jumpto/src/org/netbeans/modules/jumpto/type/GoToPanel.java --- a/jumpto/src/org/netbeans/modules/jumpto/type/GoToPanel.java +++ b/jumpto/src/org/netbeans/modules/jumpto/type/GoToPanel.java @@ -1,583 +1,586 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2010 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]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun - * Microsystems, Inc. All Rights Reserved. - * - * 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. - */ - -package org.netbeans.modules.jumpto.type; - -import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.KeyEvent; -import java.awt.event.MouseListener; -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import javax.swing.Action; -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.InputMap; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.KeyStroke; -import javax.swing.ListCellRenderer; -import javax.swing.ListModel; -import javax.swing.ListSelectionModel; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.text.BadLocationException; -import org.netbeans.modules.jumpto.SearchHistory; -import org.netbeans.spi.jumpto.type.TypeDescriptor; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; -import org.openide.util.ImageUtilities; -import org.openide.util.NbBundle; - -/** - * - * @author Petr Hrebejk - */ -public class GoToPanel extends javax.swing.JPanel { - - private static Icon WAIT_ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/jumpto/resources/wait.gif", false); // NOI18N - private static Icon WARN_ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/jumpto/resources/warning.png", false); // NOI18N - - private static final int BRIGHTER_COLOR_COMPONENT = 10; - private ContentProvider contentProvider; - private boolean containsScrollPane; - JLabel messageLabel; - private Iterable selectedTypes = Collections.emptyList(); - - private String oldText; - - // Time when the serach stared (for debugging purposes) - long time = -1; - - private final SearchHistory searchHistory; - - // handling http://netbeans.org/bugzilla/show_bug.cgi?id=203512 - // if the whole search argument (in the name JTextField) is selected and something is pasted in it's place, - // notify the DocumentListener because it will first call removeUpdate() and then inserteUpdate(). - // When removeUpdate() is called we should not call update() because it messes the messageLabel's text. - private boolean pastedFromClipboard = false; - - /** Creates new form GoToPanel */ - public GoToPanel( ContentProvider contentProvider, boolean multiSelection ) throws IOException { - this.contentProvider = contentProvider; - initComponents(); - containsScrollPane = true; - - matchesList.setSelectionMode( multiSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); - //matchesList.setPrototypeCellValue("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); - matchesList.addListSelectionListener(null); - - Color bgColorBrighter = new Color( - Math.min(getBackground().getRed() + BRIGHTER_COLOR_COMPONENT, 255), - Math.min(getBackground().getGreen() + BRIGHTER_COLOR_COMPONENT, 255), - Math.min(getBackground().getBlue() + BRIGHTER_COLOR_COMPONENT, 255) - ); - - messageLabel = new JLabel(); - messageLabel.setBackground(bgColorBrighter); - messageLabel.setHorizontalAlignment(SwingConstants.CENTER); - messageLabel.setEnabled(true); - messageLabel.setText(NbBundle.getMessage(GoToPanel.class, "TXT_NoTypesFound")); // NOI18N - messageLabel.setFont(matchesList.getFont()); - - // matchesList.setBackground( bgColorBrighter ); - // matchesScrollPane1.setBackground( bgColorBrighter ); - matchesList.setCellRenderer( contentProvider.getListCellRenderer( matchesList ) ); - contentProvider.setListModel( this, null ); - - PatternListener pl = new PatternListener( this ); - nameField.getDocument().addDocumentListener(pl); - caseSensitive.setSelected(UiOptions.GoToTypeDialog.getCaseSensitive()); - caseSensitive.addItemListener(pl); - matchesList.addListSelectionListener(pl); - - searchHistory = new SearchHistory(GoToPanel.class, nameField); - } - - @Override - public void removeNotify() { - searchHistory.saveHistory(); - super.removeNotify(); - } - - /** Sets the model from different therad - */ - public void setModel( final ListModel model ) { - // XXX measure time here - SwingUtilities.invokeLater(new Runnable() { - public void run() { - if (model.getSize() > 0 || getText() == null || getText().trim().length() == 0 ) { - matchesList.setModel(model); - matchesList.setSelectedIndex(0); - setListPanelContent(null,false); - if ( time != -1 ) { - GoToTypeAction.LOGGER.fine("Real search time " + (System.currentTimeMillis() - time) + " ms."); - time = -1; - } - } - else { - setListPanelContent( NbBundle.getMessage(GoToPanel.class, "TXT_NoTypesFound") ,false ); // NOI18N - } - } - }); - } - - /** Sets the initial text to find in case the user did not start typing yet. */ - public void setInitialText( final String text ) { - oldText = text; - SwingUtilities.invokeLater( new Runnable() { - public void run() { - String textInField = nameField.getText(); - if ( textInField == null || textInField.trim().length() == 0 ) { - nameField.setText(text); - nameField.setCaretPosition(text.length()); - nameField.setSelectionStart(0); - nameField.setSelectionEnd(text.length()); - } - } - }); - } - - public void setSelectedTypes() { - final List types = new LinkedList(); - for (Object td : matchesList.getSelectedValues()) { - types.add((TypeDescriptor)td); - } - selectedTypes = Collections.unmodifiableCollection(types); - } - - public Iterable getSelectedTypes() { - return selectedTypes; - } - - void setWarning(String warningMessage) { - if (warningMessage != null) { - jLabelWarning.setIcon(WARN_ICON); - jLabelWarning.setBorder(BorderFactory.createEmptyBorder(3, 1, 1, 1)); - } else { - jLabelWarning.setIcon(null); - jLabelWarning.setBorder(null); - } - jLabelWarning.setText(warningMessage); - } - - //handling http://netbeans.org/bugzilla/show_bug.cgi?id=178555 - public void setMouseListener(MouseListener warningMouseListener) { - if (messageLabel.getMouseListeners().length == 0) { - messageLabel.addMouseListener(warningMouseListener); - } - } - - /** This method is called from within the constructor to - * initialize the form. - * WARNING: Do NOT modify this code. The content of this method is - * always regenerated by the Form Editor. - */ - @SuppressWarnings("deprecation") - // //GEN-BEGIN:initComponents - private void initComponents() { - java.awt.GridBagConstraints gridBagConstraints; - - jLabelText = new javax.swing.JLabel(); - nameField = new javax.swing.JTextField(); - jLabelList = new javax.swing.JLabel(); - listPanel = new javax.swing.JPanel(); - matchesScrollPane1 = new javax.swing.JScrollPane(); - matchesList = new javax.swing.JList(); - jLabelWarning = new javax.swing.JLabel(); - caseSensitive = new javax.swing.JCheckBox(); - jLabelLocation = new javax.swing.JLabel(); - jTextFieldLocation = new javax.swing.JTextField(); - - setBorder(javax.swing.BorderFactory.createEmptyBorder(8, 8, 8, 8)); - setFocusable(false); - setNextFocusableComponent(nameField); - setLayout(new java.awt.GridBagLayout()); - - jLabelText.setLabelFor(nameField); - org.openide.awt.Mnemonics.setLocalizedText(jLabelText, org.openide.util.NbBundle.getMessage(GoToPanel.class, "TXT_GoToType_TypeName_Label")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); - add(jLabelText, gridBagConstraints); - jLabelText.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(GoToPanel.class, "GoToPanel.jLabelText.AccessibleContext.accessibleDescription")); // NOI18N - - nameField.setFont(new java.awt.Font("Monospaced", 0, getFontSize())); - nameField.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - nameField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - nameFieldActionPerformed(evt); - } - }); - nameField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyPressed(java.awt.event.KeyEvent evt) { - nameFieldKeyPressed(evt); - } - public void keyReleased(java.awt.event.KeyEvent evt) { - nameFieldKeyReleased(evt); - } - public void keyTyped(java.awt.event.KeyEvent evt) { - nameFieldKeyTyped(evt); - } - }); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 8, 0); - add(nameField, gridBagConstraints); - - jLabelList.setLabelFor(matchesList); - org.openide.awt.Mnemonics.setLocalizedText(jLabelList, org.openide.util.NbBundle.getMessage(GoToPanel.class, "TXT_GoToType_MatchesList_Label")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); - add(jLabelList, gridBagConstraints); - - listPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - listPanel.setName("dataPanel"); // NOI18N - listPanel.setLayout(new java.awt.BorderLayout()); - - matchesScrollPane1.setBorder(null); - matchesScrollPane1.setFocusable(false); - - matchesList.setFont(new java.awt.Font("Monospaced", 0, getFontSize())); - matchesList.setVisibleRowCount(15); - matchesList.addMouseListener(new java.awt.event.MouseAdapter() { - public void mouseReleased(java.awt.event.MouseEvent evt) { - matchesListMouseReleased(evt); - } - }); - matchesScrollPane1.setViewportView(matchesList); - matchesList.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(GoToPanel.class, "ACSD_GoToListName")); // NOI18N - matchesList.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(GoToPanel.class, "GoToPanel.matchesList.AccessibleContext.accessibleDescription")); // NOI18N - - listPanel.add(matchesScrollPane1, java.awt.BorderLayout.CENTER); - - jLabelWarning.setFocusable(false); - listPanel.add(jLabelWarning, java.awt.BorderLayout.PAGE_END); - - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 8, 0); - add(listPanel, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(caseSensitive, org.openide.util.NbBundle.getMessage(GoToPanel.class, "TXT_GoToType_CaseSensitive")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); - add(caseSensitive, gridBagConstraints); - caseSensitive.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(GoToPanel.class, "GoToPanel.caseSensitive.AccessibleContext.accessibleDescription")); // NOI18N - - jLabelLocation.setText(org.openide.util.NbBundle.getMessage(GoToPanel.class, "LBL_GoToType_LocationJLabel")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); - add(jLabelLocation, gridBagConstraints); - - jTextFieldLocation.setEditable(false); - jTextFieldLocation.setFocusable(false); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - add(jTextFieldLocation, gridBagConstraints); - }// //GEN-END:initComponents - - private void matchesListMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_matchesListMouseReleased - if ( evt.getClickCount() == 2 ) { - nameFieldActionPerformed( null ); - } - }//GEN-LAST:event_matchesListMouseReleased - - private void nameFieldKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameFieldKeyTyped - if (boundScrollingKey(evt)) { - delegateScrollingKey(evt); - } - }//GEN-LAST:event_nameFieldKeyTyped - - private void nameFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameFieldKeyReleased - if (boundScrollingKey(evt)) { - delegateScrollingKey(evt); - } - }//GEN-LAST:event_nameFieldKeyReleased - - private void nameFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameFieldKeyPressed - if (boundScrollingKey(evt)) { - delegateScrollingKey(evt); - } else { - //handling http://netbeans.org/bugzilla/show_bug.cgi?id=203512 - Object o = nameField.getInputMap().get(KeyStroke.getKeyStrokeForEvent(evt)); - if (o instanceof String) { - String action = (String) o; - if ("paste-from-clipboard".equals(action)) { - String selectedTxt = nameField.getSelectedText(); - String txt = nameField.getText(); - if (selectedTxt != null && txt != null) { - if (selectedTxt.length() == txt.length()) { - pastedFromClipboard = true; - } - } - } - } - } - }//GEN-LAST:event_nameFieldKeyPressed - - private void nameFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameFieldActionPerformed - if (contentProvider.hasValidContent()) { - contentProvider.closeDialog(); - setSelectedTypes(); - } - }//GEN-LAST:event_nameFieldActionPerformed - - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JCheckBox caseSensitive; - private javax.swing.JLabel jLabelList; - private javax.swing.JLabel jLabelLocation; - private javax.swing.JLabel jLabelText; - private javax.swing.JLabel jLabelWarning; - private javax.swing.JTextField jTextFieldLocation; - private javax.swing.JPanel listPanel; - private javax.swing.JList matchesList; - private javax.swing.JScrollPane matchesScrollPane1; - javax.swing.JTextField nameField; - // End of variables declaration//GEN-END:variables - - - private String getText() { - try { - String text = nameField.getDocument().getText(0, nameField.getDocument().getLength()); - return text; - } - catch( BadLocationException ex ) { - return null; - } - } - - private int getFontSize () { - return this.jLabelList.getFont().getSize(); - } - - public boolean isCaseSensitive () { - return this.caseSensitive.isSelected(); - } - - void setListPanelContent( String message ,boolean waitIcon ) { - - if ( message == null && !containsScrollPane ) { - listPanel.remove( messageLabel ); - listPanel.add( matchesScrollPane1 ); - containsScrollPane = true; - revalidate(); - repaint(); - } - else if ( message != null ) { - jTextFieldLocation.setText(""); - //handling http://netbeans.org/bugzilla/show_bug.cgi?id=178555 - messageLabel.setText(waitIcon - ? "" + message + "    "+NbBundle.getMessage(GoToPanel.class, "TXT_CancelSearch")+"" //NOI18N - : message); - messageLabel.setIcon( waitIcon ? WAIT_ICON : null); - if ( containsScrollPane ) { - listPanel.remove( matchesScrollPane1 ); - listPanel.add( messageLabel ); - containsScrollPane = false; - } - revalidate(); - repaint(); - } - } - - private String listActionFor(KeyEvent ev) { - InputMap map = matchesList.getInputMap(); - Object o = map.get(KeyStroke.getKeyStrokeForEvent(ev)); - if (o instanceof String) { - return (String)o; - } else { - return null; - } - } - - private boolean boundScrollingKey(KeyEvent ev) { - String action = listActionFor(ev); - // See BasicListUI, MetalLookAndFeel: - return "selectPreviousRow".equals(action) || // NOI18N - "selectNextRow".equals(action) || // NOI18N - "selectPreviousRowExtendSelection".equals(action) || //NOI18N - "selectNextRowExtendSelection".equals(action) || //NOI18N - "scrollUp".equals(action) || // NOI18N - "scrollDown".equals(action); // NOI18N - } - - private void delegateScrollingKey(KeyEvent ev) { - String action = listActionFor(ev); - - // Wrap around - if ( "selectNextRow".equals(action) && - matchesList.getSelectedIndex() == matchesList.getModel().getSize() -1 ) { - matchesList.setSelectedIndex(0); - matchesList.ensureIndexIsVisible(0); - return; - } - else if ( "selectPreviousRow".equals(action) && - matchesList.getSelectedIndex() == 0 ) { - int last = matchesList.getModel().getSize() - 1; - matchesList.setSelectedIndex(last); - matchesList.ensureIndexIsVisible(last); - return; - } - // Plain delegation - Action a = matchesList.getActionMap().get(action); - if (a != null) { - a.actionPerformed(new ActionEvent(matchesList, 0, action)); - } - } - - private static class PatternListener implements DocumentListener, ItemListener, ListSelectionListener { - - private final GoToPanel dialog; - - - PatternListener( GoToPanel dialog ) { - this.dialog = dialog; - } - - PatternListener( DocumentEvent e, GoToPanel dialog ) { - this.dialog = dialog; - } - - // DocumentListener ---------------------------------------------------- - - public void changedUpdate( DocumentEvent e ) { - update(); - } - - public void removeUpdate( DocumentEvent e ) { - // handling http://netbeans.org/bugzilla/show_bug.cgi?id=203512 - if (dialog.pastedFromClipboard) { - dialog.pastedFromClipboard = false; - } else { - update(); - } - } - - public void insertUpdate( DocumentEvent e ) { - update(); - } - - // Item Listener ------------------------------------------------------- - - public void itemStateChanged (final ItemEvent e) { - UiOptions.GoToTypeDialog.setCaseSensitive(dialog.isCaseSensitive()); - update(); - } - - // ListSelectionListener ----------------------------------------------- - - public void valueChanged(ListSelectionEvent ev) { - // got "Not computed yet" text sometimes - Object obj = dialog.matchesList.getSelectedValue(); - - if (obj instanceof TypeDescriptor) { - TypeDescriptor selectedValue = ((TypeDescriptor) obj); - if ( selectedValue != null ) { - String fileName = ""; - FileObject fo = selectedValue.getFileObject(); - if (fo != null) { - fileName = FileUtil.getFileDisplayName(fo); - } - dialog.jTextFieldLocation.setText(fileName); - } - else { - dialog.jTextFieldLocation.setText(""); - } - } else { - dialog.jTextFieldLocation.setText(""); - } - } - - private void update() { - dialog.time = System.currentTimeMillis(); - String text = dialog.getText(); - if ( dialog.oldText == null || dialog.oldText.trim().length() == 0 || !text.startsWith(dialog.oldText) ) { - dialog.setListPanelContent(NbBundle.getMessage(GoToPanel.class, "TXT_Searching"),true); // NOI18N - } - dialog.oldText = text; - dialog.contentProvider.setListModel(dialog,text); - } - - } - - - public static interface ContentProvider { - - public ListCellRenderer getListCellRenderer( JList list ); - - public void setListModel( GoToPanel panel, String text ); - - public void closeDialog(); - - public boolean hasValidContent (); - - } - -} +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2010 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]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + * + * markiewb@netbeans.org + * + * 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. + */ + +package org.netbeans.modules.jumpto.type; + +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseListener; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.KeyStroke; +import javax.swing.ListCellRenderer; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.modules.jumpto.SearchHistory; +import org.netbeans.spi.jumpto.type.TypeDescriptor; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.ImageUtilities; +import org.openide.util.NbBundle; + +/** + * + * @author Petr Hrebejk + */ +public class GoToPanel extends javax.swing.JPanel { + + private static Icon WAIT_ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/jumpto/resources/wait.gif", false); // NOI18N + private static Icon WARN_ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/jumpto/resources/warning.png", false); // NOI18N + + private static final int BRIGHTER_COLOR_COMPONENT = 10; + private ContentProvider contentProvider; + private boolean containsScrollPane; + JLabel messageLabel; + private Iterable selectedTypes = Collections.emptyList(); + + private String oldText; + + // Time when the serach stared (for debugging purposes) + long time = -1; + + private final SearchHistory searchHistory; + + // handling http://netbeans.org/bugzilla/show_bug.cgi?id=203512 + // if the whole search argument (in the name JTextField) is selected and something is pasted in it's place, + // notify the DocumentListener because it will first call removeUpdate() and then inserteUpdate(). + // When removeUpdate() is called we should not call update() because it messes the messageLabel's text. + private boolean pastedFromClipboard = false; + + /** Creates new form GoToPanel */ + public GoToPanel( ContentProvider contentProvider, boolean multiSelection ) throws IOException { + this.contentProvider = contentProvider; + initComponents(); + containsScrollPane = true; + + matchesList.setSelectionMode( multiSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); + //matchesList.setPrototypeCellValue("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); + matchesList.addListSelectionListener(null); + + Color bgColorBrighter = new Color( + Math.min(getBackground().getRed() + BRIGHTER_COLOR_COMPONENT, 255), + Math.min(getBackground().getGreen() + BRIGHTER_COLOR_COMPONENT, 255), + Math.min(getBackground().getBlue() + BRIGHTER_COLOR_COMPONENT, 255) + ); + + messageLabel = new JLabel(); + messageLabel.setBackground(bgColorBrighter); + messageLabel.setHorizontalAlignment(SwingConstants.CENTER); + messageLabel.setEnabled(true); + messageLabel.setText(NbBundle.getMessage(GoToPanel.class, "TXT_NoTypesFound")); // NOI18N + messageLabel.setFont(matchesList.getFont()); + + // matchesList.setBackground( bgColorBrighter ); + // matchesScrollPane1.setBackground( bgColorBrighter ); + matchesList.setCellRenderer( contentProvider.getListCellRenderer( matchesList, nameField.getDocument())); + contentProvider.setListModel( this, null ); + + PatternListener pl = new PatternListener( this ); + nameField.getDocument().addDocumentListener(pl); + caseSensitive.setSelected(UiOptions.GoToTypeDialog.getCaseSensitive()); + caseSensitive.addItemListener(pl); + matchesList.addListSelectionListener(pl); + + searchHistory = new SearchHistory(GoToPanel.class, nameField); + } + + @Override + public void removeNotify() { + searchHistory.saveHistory(); + super.removeNotify(); + } + + /** Sets the model from different therad + */ + public void setModel( final ListModel model ) { + // XXX measure time here + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (model.getSize() > 0 || getText() == null || getText().trim().length() == 0 ) { + matchesList.setModel(model); + matchesList.setSelectedIndex(0); + setListPanelContent(null,false); + if ( time != -1 ) { + GoToTypeAction.LOGGER.fine("Real search time " + (System.currentTimeMillis() - time) + " ms."); + time = -1; + } + } + else { + setListPanelContent( NbBundle.getMessage(GoToPanel.class, "TXT_NoTypesFound") ,false ); // NOI18N + } + } + }); + } + + /** Sets the initial text to find in case the user did not start typing yet. */ + public void setInitialText( final String text ) { + oldText = text; + SwingUtilities.invokeLater( new Runnable() { + public void run() { + String textInField = nameField.getText(); + if ( textInField == null || textInField.trim().length() == 0 ) { + nameField.setText(text); + nameField.setCaretPosition(text.length()); + nameField.setSelectionStart(0); + nameField.setSelectionEnd(text.length()); + } + } + }); + } + + public void setSelectedTypes() { + final List types = new LinkedList(); + for (Object td : matchesList.getSelectedValues()) { + types.add((TypeDescriptor)td); + } + selectedTypes = Collections.unmodifiableCollection(types); + } + + public Iterable getSelectedTypes() { + return selectedTypes; + } + + void setWarning(String warningMessage) { + if (warningMessage != null) { + jLabelWarning.setIcon(WARN_ICON); + jLabelWarning.setBorder(BorderFactory.createEmptyBorder(3, 1, 1, 1)); + } else { + jLabelWarning.setIcon(null); + jLabelWarning.setBorder(null); + } + jLabelWarning.setText(warningMessage); + } + + //handling http://netbeans.org/bugzilla/show_bug.cgi?id=178555 + public void setMouseListener(MouseListener warningMouseListener) { + if (messageLabel.getMouseListeners().length == 0) { + messageLabel.addMouseListener(warningMouseListener); + } + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("deprecation") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + jLabelText = new javax.swing.JLabel(); + nameField = new javax.swing.JTextField(); + jLabelList = new javax.swing.JLabel(); + listPanel = new javax.swing.JPanel(); + matchesScrollPane1 = new javax.swing.JScrollPane(); + matchesList = new javax.swing.JList(); + jLabelWarning = new javax.swing.JLabel(); + caseSensitive = new javax.swing.JCheckBox(); + jLabelLocation = new javax.swing.JLabel(); + jTextFieldLocation = new javax.swing.JTextField(); + + setBorder(javax.swing.BorderFactory.createEmptyBorder(8, 8, 8, 8)); + setFocusable(false); + setNextFocusableComponent(nameField); + setLayout(new java.awt.GridBagLayout()); + + jLabelText.setLabelFor(nameField); + org.openide.awt.Mnemonics.setLocalizedText(jLabelText, org.openide.util.NbBundle.getMessage(GoToPanel.class, "TXT_GoToType_TypeName_Label")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); + add(jLabelText, gridBagConstraints); + jLabelText.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(GoToPanel.class, "GoToPanel.jLabelText.AccessibleContext.accessibleDescription")); // NOI18N + + nameField.setFont(new java.awt.Font("Monospaced", 0, getFontSize())); + nameField.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + nameField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + nameFieldActionPerformed(evt); + } + }); + nameField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyPressed(java.awt.event.KeyEvent evt) { + nameFieldKeyPressed(evt); + } + public void keyReleased(java.awt.event.KeyEvent evt) { + nameFieldKeyReleased(evt); + } + public void keyTyped(java.awt.event.KeyEvent evt) { + nameFieldKeyTyped(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 8, 0); + add(nameField, gridBagConstraints); + + jLabelList.setLabelFor(matchesList); + org.openide.awt.Mnemonics.setLocalizedText(jLabelList, org.openide.util.NbBundle.getMessage(GoToPanel.class, "TXT_GoToType_MatchesList_Label")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); + add(jLabelList, gridBagConstraints); + + listPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + listPanel.setName("dataPanel"); // NOI18N + listPanel.setLayout(new java.awt.BorderLayout()); + + matchesScrollPane1.setBorder(null); + matchesScrollPane1.setFocusable(false); + + matchesList.setFont(new java.awt.Font("Monospaced", 0, getFontSize())); + matchesList.setVisibleRowCount(15); + matchesList.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseReleased(java.awt.event.MouseEvent evt) { + matchesListMouseReleased(evt); + } + }); + matchesScrollPane1.setViewportView(matchesList); + matchesList.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(GoToPanel.class, "ACSD_GoToListName")); // NOI18N + matchesList.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(GoToPanel.class, "GoToPanel.matchesList.AccessibleContext.accessibleDescription")); // NOI18N + + listPanel.add(matchesScrollPane1, java.awt.BorderLayout.CENTER); + + jLabelWarning.setFocusable(false); + listPanel.add(jLabelWarning, java.awt.BorderLayout.PAGE_END); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 8, 0); + add(listPanel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(caseSensitive, org.openide.util.NbBundle.getMessage(GoToPanel.class, "TXT_GoToType_CaseSensitive")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); + add(caseSensitive, gridBagConstraints); + caseSensitive.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(GoToPanel.class, "GoToPanel.caseSensitive.AccessibleContext.accessibleDescription")); // NOI18N + + jLabelLocation.setText(org.openide.util.NbBundle.getMessage(GoToPanel.class, "LBL_GoToType_LocationJLabel")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 4, 0); + add(jLabelLocation, gridBagConstraints); + + jTextFieldLocation.setEditable(false); + jTextFieldLocation.setFocusable(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + add(jTextFieldLocation, gridBagConstraints); + }// //GEN-END:initComponents + + private void matchesListMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_matchesListMouseReleased + if ( evt.getClickCount() == 2 ) { + nameFieldActionPerformed( null ); + } + }//GEN-LAST:event_matchesListMouseReleased + + private void nameFieldKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameFieldKeyTyped + if (boundScrollingKey(evt)) { + delegateScrollingKey(evt); + } + }//GEN-LAST:event_nameFieldKeyTyped + + private void nameFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameFieldKeyReleased + if (boundScrollingKey(evt)) { + delegateScrollingKey(evt); + } + }//GEN-LAST:event_nameFieldKeyReleased + + private void nameFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameFieldKeyPressed + if (boundScrollingKey(evt)) { + delegateScrollingKey(evt); + } else { + //handling http://netbeans.org/bugzilla/show_bug.cgi?id=203512 + Object o = nameField.getInputMap().get(KeyStroke.getKeyStrokeForEvent(evt)); + if (o instanceof String) { + String action = (String) o; + if ("paste-from-clipboard".equals(action)) { + String selectedTxt = nameField.getSelectedText(); + String txt = nameField.getText(); + if (selectedTxt != null && txt != null) { + if (selectedTxt.length() == txt.length()) { + pastedFromClipboard = true; + } + } + } + } + } + }//GEN-LAST:event_nameFieldKeyPressed + + private void nameFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameFieldActionPerformed + if (contentProvider.hasValidContent()) { + contentProvider.closeDialog(); + setSelectedTypes(); + } + }//GEN-LAST:event_nameFieldActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox caseSensitive; + private javax.swing.JLabel jLabelList; + private javax.swing.JLabel jLabelLocation; + private javax.swing.JLabel jLabelText; + private javax.swing.JLabel jLabelWarning; + private javax.swing.JTextField jTextFieldLocation; + private javax.swing.JPanel listPanel; + private javax.swing.JList matchesList; + private javax.swing.JScrollPane matchesScrollPane1; + javax.swing.JTextField nameField; + // End of variables declaration//GEN-END:variables + + + private String getText() { + try { + String text = nameField.getDocument().getText(0, nameField.getDocument().getLength()); + return text; + } + catch( BadLocationException ex ) { + return null; + } + } + + private int getFontSize () { + return this.jLabelList.getFont().getSize(); + } + + public boolean isCaseSensitive () { + return this.caseSensitive.isSelected(); + } + + void setListPanelContent( String message ,boolean waitIcon ) { + + if ( message == null && !containsScrollPane ) { + listPanel.remove( messageLabel ); + listPanel.add( matchesScrollPane1 ); + containsScrollPane = true; + revalidate(); + repaint(); + } + else if ( message != null ) { + jTextFieldLocation.setText(""); + //handling http://netbeans.org/bugzilla/show_bug.cgi?id=178555 + messageLabel.setText(waitIcon + ? "" + message + "    "+NbBundle.getMessage(GoToPanel.class, "TXT_CancelSearch")+"" //NOI18N + : message); + messageLabel.setIcon( waitIcon ? WAIT_ICON : null); + if ( containsScrollPane ) { + listPanel.remove( matchesScrollPane1 ); + listPanel.add( messageLabel ); + containsScrollPane = false; + } + revalidate(); + repaint(); + } + } + + private String listActionFor(KeyEvent ev) { + InputMap map = matchesList.getInputMap(); + Object o = map.get(KeyStroke.getKeyStrokeForEvent(ev)); + if (o instanceof String) { + return (String)o; + } else { + return null; + } + } + + private boolean boundScrollingKey(KeyEvent ev) { + String action = listActionFor(ev); + // See BasicListUI, MetalLookAndFeel: + return "selectPreviousRow".equals(action) || // NOI18N + "selectNextRow".equals(action) || // NOI18N + "selectPreviousRowExtendSelection".equals(action) || //NOI18N + "selectNextRowExtendSelection".equals(action) || //NOI18N + "scrollUp".equals(action) || // NOI18N + "scrollDown".equals(action); // NOI18N + } + + private void delegateScrollingKey(KeyEvent ev) { + String action = listActionFor(ev); + + // Wrap around + if ( "selectNextRow".equals(action) && + matchesList.getSelectedIndex() == matchesList.getModel().getSize() -1 ) { + matchesList.setSelectedIndex(0); + matchesList.ensureIndexIsVisible(0); + return; + } + else if ( "selectPreviousRow".equals(action) && + matchesList.getSelectedIndex() == 0 ) { + int last = matchesList.getModel().getSize() - 1; + matchesList.setSelectedIndex(last); + matchesList.ensureIndexIsVisible(last); + return; + } + // Plain delegation + Action a = matchesList.getActionMap().get(action); + if (a != null) { + a.actionPerformed(new ActionEvent(matchesList, 0, action)); + } + } + + private static class PatternListener implements DocumentListener, ItemListener, ListSelectionListener { + + private final GoToPanel dialog; + + + PatternListener( GoToPanel dialog ) { + this.dialog = dialog; + } + + PatternListener( DocumentEvent e, GoToPanel dialog ) { + this.dialog = dialog; + } + + // DocumentListener ---------------------------------------------------- + + public void changedUpdate( DocumentEvent e ) { + update(); + } + + public void removeUpdate( DocumentEvent e ) { + // handling http://netbeans.org/bugzilla/show_bug.cgi?id=203512 + if (dialog.pastedFromClipboard) { + dialog.pastedFromClipboard = false; + } else { + update(); + } + } + + public void insertUpdate( DocumentEvent e ) { + update(); + } + + // Item Listener ------------------------------------------------------- + + public void itemStateChanged (final ItemEvent e) { + UiOptions.GoToTypeDialog.setCaseSensitive(dialog.isCaseSensitive()); + update(); + } + + // ListSelectionListener ----------------------------------------------- + + public void valueChanged(ListSelectionEvent ev) { + // got "Not computed yet" text sometimes + Object obj = dialog.matchesList.getSelectedValue(); + + if (obj instanceof TypeDescriptor) { + TypeDescriptor selectedValue = ((TypeDescriptor) obj); + if ( selectedValue != null ) { + String fileName = ""; + FileObject fo = selectedValue.getFileObject(); + if (fo != null) { + fileName = FileUtil.getFileDisplayName(fo); + } + dialog.jTextFieldLocation.setText(fileName); + } + else { + dialog.jTextFieldLocation.setText(""); + } + } else { + dialog.jTextFieldLocation.setText(""); + } + } + + private void update() { + dialog.time = System.currentTimeMillis(); + String text = dialog.getText(); + if ( dialog.oldText == null || dialog.oldText.trim().length() == 0 || !text.startsWith(dialog.oldText) ) { + dialog.setListPanelContent(NbBundle.getMessage(GoToPanel.class, "TXT_Searching"),true); // NOI18N + } + dialog.oldText = text; + dialog.contentProvider.setListModel(dialog,text); + } + + } + + + public static interface ContentProvider { + + public ListCellRenderer getListCellRenderer( JList list, Document document); + + public void setListModel( GoToPanel panel, String text ); + + public void closeDialog(); + + public boolean hasValidContent (); + + } + +} diff --git a/jumpto/src/org/netbeans/modules/jumpto/type/GoToTypeAction.java b/jumpto/src/org/netbeans/modules/jumpto/type/GoToTypeAction.java --- a/jumpto/src/org/netbeans/modules/jumpto/type/GoToTypeAction.java +++ b/jumpto/src/org/netbeans/modules/jumpto/type/GoToTypeAction.java @@ -1,864 +1,904 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2011 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]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2011 Sun - * Microsystems, Inc. All Rights Reserved. - * - * 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. - */ - -package org.netbeans.modules.jumpto.type; - -import org.netbeans.spi.jumpto.type.SearchType; -import org.netbeans.spi.jumpto.type.TypeProvider; -import org.netbeans.spi.jumpto.type.TypeDescriptor; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Color; -import java.awt.Container; -import java.awt.Dialog; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; -import javax.swing.AbstractAction; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.ListCellRenderer; -import javax.swing.JEditorPane; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JViewport; -import javax.swing.ListModel; -import javax.swing.SwingUtilities; -import javax.swing.event.ChangeEvent; -import org.netbeans.api.jumpto.type.TypeBrowser; -import org.netbeans.api.project.ui.OpenProjects; -import org.netbeans.modules.jumpto.EntitiesListCellRenderer; -import org.netbeans.modules.jumpto.file.LazyListModel; -import org.netbeans.modules.sampler.Sampler; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.ErrorManager; -import org.openide.cookies.EditorCookie; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; -import org.openide.nodes.Node; -import org.openide.text.NbDocument; -import org.openide.util.Exceptions; -import org.openide.util.HelpCtx; -import org.openide.util.ImageUtilities; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.openide.util.RequestProcessor; -import org.openide.util.Utilities; -import org.openide.windows.TopComponent; - -/** - * XXX split into action and support class, left this just to minimize diff - * XXX Icons - * XXX Don't look for all projects (do it lazy in filter or renderer) - * @author Petr Hrebejk - */ -public class GoToTypeAction extends AbstractAction implements GoToPanel.ContentProvider, LazyListModel.Filter { - - static final Logger LOGGER = Logger.getLogger(GoToTypeAction.class.getName()); // Used from the panel as well - - private SearchType nameKind; - private static ListModel EMPTY_LIST_MODEL = new DefaultListModel(); - private static final RequestProcessor rp = new RequestProcessor ("GoToTypeAction-RequestProcessor",1); //NOI18N - private static final RequestProcessor PROFILE_RP = new RequestProcessor("GoToTypeAction-Profile",1); //NOI18N - private Worker running; - private RequestProcessor.Task task; - GoToPanel panel; - private Dialog dialog; - private JButton okButton; - private Collection typeProviders; - private final Collection implicitTypeProviders; - private final TypeBrowser.Filter typeFilter; - private final String title; - private final boolean multiSelection; - - /** Creates a new instance of OpenTypeAction */ - public GoToTypeAction() { - this( - NbBundle.getMessage( GoToTypeAction.class, "DLG_GoToType" ), - null, - true - ); - } - - public GoToTypeAction(String title, TypeBrowser.Filter typeFilter, boolean multiSelection, TypeProvider... typeProviders) { - super( NbBundle.getMessage( GoToTypeAction.class,"TXT_GoToType") ); - putValue("PopupMenuText", NbBundle.getBundle(GoToTypeAction.class).getString("editor-popup-TXT_GoToType")); // NOI18N - this.title = title; - this.typeFilter = typeFilter; - this.implicitTypeProviders = typeProviders.length == 0 ? null : Collections.unmodifiableCollection(Arrays.asList(typeProviders)); - this.multiSelection = multiSelection; - } - - @Override - public void actionPerformed( ActionEvent e ) { - for (TypeDescriptor td : getSelectedTypes()) { - td.open(); - } - } - - public Iterable getSelectedTypes() { - return getSelectedTypes(true); - } - - public Iterable getSelectedTypes(final boolean visible) { - return getSelectedTypes(visible, null); - } - - public Iterable getSelectedTypes(final boolean visible, String initSearchText) { - Iterable result = Collections.emptyList(); - try { - panel = new GoToPanel(this, multiSelection); - dialog = createDialog(panel); - - if (initSearchText != null) { - panel.setInitialText(initSearchText); - } else { - Node[] arr = TopComponent.getRegistry ().getActivatedNodes(); - if (arr.length > 0) { - EditorCookie ec = arr[0].getCookie (EditorCookie.class); - if (ec != null) { - JEditorPane recentPane = NbDocument.findRecentEditorPane(ec); - if (recentPane != null) { - initSearchText = org.netbeans.editor.Utilities.getSelectionOrIdentifier(recentPane); - if (initSearchText != null && org.openide.util.Utilities.isJavaIdentifier(initSearchText)) { - panel.setInitialText(initSearchText); - } else { - panel.setInitialText(arr[0].getName()); - } - } - } - } - } - - dialog.setVisible(visible); - result = panel.getSelectedTypes(); - - } catch (IOException ex) { - ErrorManager.getDefault().notify(ex); - } - return result; - } - - @Override - public boolean isEnabled () { - return OpenProjects.getDefault().getOpenProjects().length>0; - } - - @Override - public boolean accept(Object obj) { - return typeFilter == null ? true : typeFilter.accept((TypeDescriptor) obj); - } - - @Override - public void scheduleUpdate(Runnable run) { - SwingUtilities.invokeLater(run); - } - - // Implementation of content provider -------------------------------------- - - - @Override - public ListCellRenderer getListCellRenderer( JList list ) { - return new Renderer( list ); - } - - - @Override - public void setListModel( GoToPanel panel, String text ) { - assert SwingUtilities.isEventDispatchThread(); - if (okButton != null) { - okButton.setEnabled (false); - } - //handling http://netbeans.org/bugzilla/show_bug.cgi?id=178555 - //add a MouseListener to the messageLabel JLabel so that the search can be cancelled without exiting the dialog - final GoToPanel goToPanel = panel; - final MouseListener warningMouseListener = new java.awt.event.MouseAdapter() { - - @Override - public void mouseClicked(java.awt.event.MouseEvent evt) { - if (running != null) { - running.cancel(); - task.cancel(); - running = null; - } - goToPanel.setListPanelContent(NbBundle.getMessage(GoToPanel.class, "TXT_SearchCanceled"),false); // NOI18N - } - }; - panel.setMouseListener(warningMouseListener); - if ( running != null ) { - running.cancel(); - task.cancel(); - running = null; - } - - if ( text == null ) { - panel.setModel(EMPTY_LIST_MODEL); - return; - } - - boolean exact = text.endsWith(" "); // NOI18N - - text = text.trim(); - - if ( text.length() == 0) { - panel.setModel(EMPTY_LIST_MODEL); - return; - } - - int wildcard = containsWildCard(text); - - if (exact) { - //nameKind = panel.isCaseSensitive() ? SearchType.EXACT_NAME : SearchType.CASE_INSENSITIVE_EXACT_NAME; - nameKind = SearchType.EXACT_NAME; - } - else if ((isAllUpper(text) && text.length() > 1) || isCamelCase(text)) { - nameKind = SearchType.CAMEL_CASE; - } - else if (wildcard != -1) { - nameKind = panel.isCaseSensitive() ? SearchType.REGEXP : SearchType.CASE_INSENSITIVE_REGEXP; - } - else { - nameKind = panel.isCaseSensitive() ? SearchType.PREFIX : SearchType.CASE_INSENSITIVE_PREFIX; - } - - // Compute in other thread - running = new Worker( text ); - task = rp.post( running, 220); - if ( panel.time != -1 ) { - LOGGER.log( Level.FINE, "Worker posted after {0} ms.", System.currentTimeMillis() - panel.time ); //NOI18N - } - } - - @Override - public void closeDialog() { - // Closing event can be sent several times. - if (dialog == null ) { // #172568 - return; // OK - the dialog has already been closed. - } - dialog.setVisible( false ); - cleanup(); - } - - @Override - public boolean hasValidContent () { - return this.okButton != null && this.okButton.isEnabled(); - } - - // Private methods --------------------------------------------------------- - - public static boolean isAllUpper( String text ) { - for( int i = 0; i < text.length(); i++ ) { - if ( !Character.isUpperCase( text.charAt( i ) ) ) { - return false; - } - } - - return true; - } - - public static int containsWildCard( String text ) { - for( int i = 0; i < text.length(); i++ ) { - if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N - return i; - } - } - return -1; - } - - private static Pattern camelCasePattern = Pattern.compile("(?:\\p{javaUpperCase}(?:\\p{javaLowerCase}|\\p{Digit}|\\.|\\$)*){2,}"); // NOI18N - - public static boolean isCamelCase(String text) { - return camelCasePattern.matcher(text).matches(); - } - - - /** Creates the dialog to show - */ - private Dialog createDialog( final GoToPanel panel) { - - okButton = new JButton (NbBundle.getMessage(GoToTypeAction.class, "CTL_OK")); - okButton.getAccessibleContext().setAccessibleDescription(okButton.getText()); - okButton.setEnabled (false); - panel.getAccessibleContext().setAccessibleName( NbBundle.getMessage( GoToTypeAction.class, "AN_GoToType") ); //NOI18N - panel.getAccessibleContext().setAccessibleDescription( NbBundle.getMessage( GoToTypeAction.class, "AD_GoToType") ); //NOI18N - - DialogDescriptor dialogDescriptor = new DialogDescriptor( - panel, // innerPane - title, // displayName - true, - new Object[] {okButton, DialogDescriptor.CANCEL_OPTION}, - okButton, - DialogDescriptor.DEFAULT_ALIGN, - HelpCtx.DEFAULT_HELP, - new DialogButtonListener( panel ) ); // Action listener - - dialogDescriptor.setClosingOptions(new Object[] {okButton, DialogDescriptor.CANCEL_OPTION}); - - // panel.addPropertyChangeListener( new HelpCtxChangeListener( dialogDescriptor, helpCtx ) ); -// if ( panel instanceof HelpCtx.Provider ) { -// dialogDescriptor.setHelpCtx( ((HelpCtx.Provider)panel).getHelpCtx() ); -// } - - Dialog d = DialogDisplayer.getDefault().createDialog( dialogDescriptor ); - - // Set size when needed - final int width = UiOptions.GoToTypeDialog.getWidth(); - final int height = UiOptions.GoToTypeDialog.getHeight(); - if (width != -1 && height != -1) { - d.setPreferredSize(new Dimension(width,height)); - } - - // Center the dialog after the size changed. - Rectangle r = Utilities.getUsableScreenBounds(); - int maxW = (r.width * 9) / 10; - int maxH = (r.height * 9) / 10; - final Dimension dim = d.getPreferredSize(); - dim.width = Math.min(dim.width, maxW); - dim.height = Math.min(dim.height, maxH); - d.setBounds(Utilities.findCenterBounds(dim)); - initialDimension = dim; - d.addWindowListener(new WindowAdapter() { - public @Override void windowClosed(WindowEvent e) { - cleanup(); - } - }); - - return d; - - } - - private Dimension initialDimension; - - private void cleanup() { - assert SwingUtilities.isEventDispatchThread(); - if ( GoToTypeAction.this.dialog != null ) { // Closing event for some reson sent twice - - // Save dialog size only when changed - final int currentWidth = dialog.getWidth(); - final int currentHeight = dialog.getHeight(); - if (initialDimension != null && (initialDimension.width != currentWidth || initialDimension.height != currentHeight)) { - UiOptions.GoToTypeDialog.setHeight(currentHeight); - UiOptions.GoToTypeDialog.setWidth(currentWidth); - } - initialDimension = null; - // Clean caches - GoToTypeAction.this.dialog.dispose(); - GoToTypeAction.this.dialog = null; - //1st) Cancel current task - if ( running != null ) { - running.cancel(); - task.cancel(); - running = null; - } - //2nd do clean up in the same thread as init to prevent races - rp.submit(new Runnable(){ - @Override - public void run() { - assert rp.isRequestProcessorThread(); - if (typeProviders != null) { - for (TypeProvider provider : typeProviders) { - provider.cleanup(); - } - typeProviders = null; - } - } - }); - } - } - - // Private classes --------------------------------------------------------- - - - - private class Worker implements Runnable { - - private volatile boolean isCanceled = false; - private volatile TypeProvider current; - private final String text; - - private final long createTime; - - public Worker( String text ) { - this.text = text; - this.createTime = System.currentTimeMillis(); - LOGGER.log( Level.FINE, "Worker for {0} - created after {1} ms.", //NOI18N - new Object[]{ - text, - System.currentTimeMillis() - panel.time - }); - } - - @Override - public void run() { - for (;;) { - final int[] retry = new int[1]; - - Profile profile = initializeProfiling(); - try { - LOGGER.log( Level.FINE, "Worker for {0} - started {1} ms.", //NOI18N - new Object[]{ - text, - System.currentTimeMillis() - createTime - }); - - final List types = getTypeNames(text, retry); - if ( isCanceled ) { - LOGGER.log( Level.FINE, "Worker for {0} exited after cancel {1} ms.", //NOI18N - new Object[]{ - text, - System.currentTimeMillis() - createTime - }); - return; - } - ListModel model = Models.fromList(types); - if (typeFilter != null) { - model = LazyListModel.create(model, GoToTypeAction.this, 0.1, "Not computed yet"); - } - final ListModel fmodel = model; - if ( isCanceled ) { - LOGGER.log( Level.FINE, "Worker for {0} exited after cancel {1} ms.", //NOI18N - new Object[]{ - text, - System.currentTimeMillis() - createTime - }); - return; - } - - if ( !isCanceled && fmodel != null ) { - LOGGER.log( Level.FINE, "Worker for text {0} finished after {1} ms.", //NOI18N - new Object[]{ - text, - System.currentTimeMillis() - createTime - }); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - panel.setModel(fmodel); - if (okButton != null && !types.isEmpty()) { - okButton.setEnabled (true); - } - } - }); - } - } finally { - if (profile != null) { - try { - profile.stop(); - } catch (Exception ex) { - LOGGER.log(Level.INFO, "Cannot stop profiling", ex); //NOI18N - } - } - } - - if (retry[0] > 0) { - try { - Thread.sleep(retry[0]); - } catch (InterruptedException ex) { - Exceptions.printStackTrace(ex); - } - } else { - return; - } - } // for - } - - public void cancel() { - if ( panel.time != -1 ) { - LOGGER.log( Level.FINE, "Worker for text {0} canceled after {1} ms.", //NOI18N - new Object[]{ - text, - System.currentTimeMillis() - createTime - }); - } - TypeProvider _provider; - synchronized (this) { - isCanceled = true; - _provider = current; - } - if (_provider != null) { - _provider.cancel(); - } - } - - @SuppressWarnings("unchecked") - private List getTypeNames(String text, int[] retry) { - // TODO: Search twice, first for current project, then for all projects - List items; - // Multiple providers: merge results - items = new ArrayList(128); - String[] message = new String[1]; - TypeProvider.Context context = TypeProviderAccessor.DEFAULT.createContext(null, text, nameKind); - TypeProvider.Result result = TypeProviderAccessor.DEFAULT.createResult(items, message); - assert rp.isRequestProcessorThread(); - if (typeProviders == null) { - typeProviders = implicitTypeProviders != null ? implicitTypeProviders : Lookup.getDefault().lookupAll(TypeProvider.class); - } - for (TypeProvider provider : typeProviders) { - if (isCanceled) { - return null; - } - current = provider; - long start = System.currentTimeMillis(); - try { - LOGGER.log(Level.FINE, "Calling TypeProvider: {0}", provider); //NOI18N - provider.computeTypeNames(context, result); - } finally { - current = null; - } - long delta = System.currentTimeMillis() - start; - LOGGER.log(Level.FINE, "Provider ''{0}'' took {1} ms.", //NOI18N - new Object[]{ - provider.getDisplayName(), - delta - }); - } - retry[0] = TypeProviderAccessor.DEFAULT.getRetry(result); - if ( !isCanceled ) { - //time = System.currentTimeMillis(); - Collections.sort(items, new TypeComparator()); - panel.setWarning(message[0]); - //sort += System.currentTimeMillis() - time; - //LOGGER.fine("PERF - " + " GSS: " + gss + " GSB " + gsb + " CP: " + cp + " SFB: " + sfb + " GTN: " + gtn + " ADD: " + add + " SORT: " + sort ); - return items; - } - else { - return null; - } - } - } - - private static class MyPanel extends JPanel { - - private TypeDescriptor td; - - void setDescriptor(TypeDescriptor td) { - this.td = td; - // since the same component is reused for dirrerent list itens, - // null the tool tip - putClientProperty(TOOL_TIP_TEXT_KEY, null); - } - - @Override - public String getToolTipText() { - // the tool tip is gotten from the descriptor - // and cached in the standard TOOL_TIP_TEXT_KEY property - String text = (String) getClientProperty(TOOL_TIP_TEXT_KEY); - if( text == null ) { - if( td != null ) { - FileObject fo = td.getFileObject(); - if (fo != null) { - text = FileUtil.getFileDisplayName(fo); - } - } - putClientProperty(TOOL_TIP_TEXT_KEY, text); - } - return text; - } - } - - final void waitSearchFinished() { - assert SwingUtilities.isEventDispatchThread(); - task.waitFinished(); - } - - private static final class Renderer extends EntitiesListCellRenderer { - - private MyPanel rendererComponent; - private JLabel jlName = new JLabel(); - private JLabel jlPkg = new JLabel(); - private JLabel jlPrj = new JLabel(); - private int DARKER_COLOR_COMPONENT = 5; - private int LIGHTER_COLOR_COMPONENT = 80; - private Color fgColor; - private Color fgColorLighter; - private Color bgColor; - private Color bgColorDarker; - private Color bgSelectionColor; - private Color fgSelectionColor; - - private JList jList; - - @SuppressWarnings("LeakingThisInConstructor") - public Renderer( JList list ) { - - jList = list; - - Container container = list.getParent(); - if ( container instanceof JViewport ) { - ((JViewport)container).addChangeListener(this); - stateChanged(new ChangeEvent(container)); - } - - rendererComponent = new MyPanel(); - rendererComponent.setLayout(new GridBagLayout()); - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 1; - c.gridheight = 1; - c.fill = GridBagConstraints.NONE; - c.weightx = 0; - c.anchor = GridBagConstraints.WEST; - c.insets = new Insets (0,0,0,7); - rendererComponent.add( jlName, c); - - c = new GridBagConstraints(); - c.gridx = 1; - c.gridy = 0; - c.gridwidth = 1; - c.gridheight = 1; - c.fill = GridBagConstraints.HORIZONTAL; - c.weightx = 0.1; - c.anchor = GridBagConstraints.WEST; - c.insets = new Insets (0,0,0,7); - rendererComponent.add( jlPkg, c); - - c = new GridBagConstraints(); - c.gridx = 2; - c.gridy = 0; - c.gridwidth = 1; - c.gridheight = 1; - c.fill = GridBagConstraints.NONE; - c.weightx = 0; - c.anchor = GridBagConstraints.EAST; - rendererComponent.add( jlPrj, c); - - - jlName.setOpaque(false); - jlPkg.setOpaque(false); - jlPrj.setOpaque(false); - - jlName.setFont(list.getFont()); - jlPkg.setFont(list.getFont()); - jlPrj.setFont(list.getFont()); - - - jlPrj.setHorizontalAlignment(RIGHT); - jlPrj.setHorizontalTextPosition(LEFT); - - // setFont( list.getFont() ); - fgColor = list.getForeground(); - fgColorLighter = new Color( - Math.min( 255, fgColor.getRed() + LIGHTER_COLOR_COMPONENT), - Math.min( 255, fgColor.getGreen() + LIGHTER_COLOR_COMPONENT), - Math.min( 255, fgColor.getBlue() + LIGHTER_COLOR_COMPONENT) - ); - - bgColor = list.getBackground(); - bgColorDarker = new Color( - Math.abs(bgColor.getRed() - DARKER_COLOR_COMPONENT), - Math.abs(bgColor.getGreen() - DARKER_COLOR_COMPONENT), - Math.abs(bgColor.getBlue() - DARKER_COLOR_COMPONENT) - ); - bgSelectionColor = list.getSelectionBackground(); - fgSelectionColor = list.getSelectionForeground(); - } - - public @Override Component getListCellRendererComponent( JList list, - Object value, - int index, - boolean isSelected, - boolean hasFocus) { - - // System.out.println("Renderer for index " + index ); - - int height = list.getFixedCellHeight(); - int width = list.getFixedCellWidth() - 1; - - width = width < 200 ? 200 : width; - - // System.out.println("w, h " + width + ", " + height ); - - Dimension size = new Dimension( width, height ); - rendererComponent.setMaximumSize(size); - rendererComponent.setPreferredSize(size); - - if ( isSelected ) { - jlName.setForeground(fgSelectionColor); - jlPkg.setForeground(fgSelectionColor); - jlPrj.setForeground(fgSelectionColor); - rendererComponent.setBackground(bgSelectionColor); - } - else { - jlName.setForeground(fgColor); - jlPkg.setForeground(fgColorLighter); - jlPrj.setForeground(fgColor); - rendererComponent.setBackground( index % 2 == 0 ? bgColor : bgColorDarker ); - } - - if ( value instanceof TypeDescriptor ) { - long time = System.currentTimeMillis(); - TypeDescriptor td = (TypeDescriptor)value; - jlName.setIcon(td.getIcon()); - jlName.setText(td.getTypeName()); - jlPkg.setText(td.getContextName()); - setProjectName(jlPrj, td.getProjectName()); - jlPrj.setIcon(td.getProjectIcon()); - rendererComponent.setDescriptor(td); - LOGGER.log(Level.FINE, " Time in paint {0} ms.", System.currentTimeMillis() - time); //NOI18N - } - else { - jlName.setText( value.toString() ); - } - - return rendererComponent; - } - - @Override - public void stateChanged(ChangeEvent event) { - - JViewport jv = (JViewport)event.getSource(); - - jlName.setText( "Sample" ); // NOI18N - //jlName.setIcon(UiUtils.getElementIcon(ElementKind.CLASS, null)); - jlName.setIcon(ImageUtilities.loadImageIcon("org/netbeans/modules/jumpto/type/sample.png", false)); //NOI18N - - jList.setFixedCellHeight(jlName.getPreferredSize().height); - jList.setFixedCellWidth(jv.getExtentSize().width); - } - - } // Renderer - - private class DialogButtonListener implements ActionListener { - - private GoToPanel panel; - - public DialogButtonListener( GoToPanel panel ) { - this.panel = panel; - } - - @Override - public void actionPerformed(ActionEvent e) { - if ( e.getSource() == okButton) { - panel.setSelectedTypes(); - } - } - - } - - - private Profile initializeProfiling() { - boolean assertsOn = false; - assert assertsOn = true; - if (!assertsOn) { - return null; - } - - Sampler profiler = Sampler.createSampler("jumpto"); //NOI18N - if (profiler == null) { - return null; - } - return new Profile(profiler).start(); - } - - private class Profile implements Runnable { - private final long time; - private volatile Sampler profiler; - private volatile boolean profiling; - - public Profile(Sampler profiler) { - time = System.currentTimeMillis(); - this.profiler = profiler; - } - - Profile start() { - PROFILE_RP.post(this, 3000); // 3s - return this; - } - - @Override - public synchronized void run() { - if (profiler != null) { - profiling = true; - profiler.start(); - } - } - - private synchronized void stop() throws Exception { - long delta = System.currentTimeMillis() - time; - - Sampler ss = profiler; - profiler = null; - if (!profiling) { - return; - } - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(out); - ss.stopAndWriteTo(dos); - dos.close(); - if (dos.size() > 0) { - Object[] params = new Object[]{out.toByteArray(), delta, "GoToType" }; //NOI18N - Logger.getLogger("org.netbeans.ui.performance").log(Level.CONFIG, "Slowness detected", params); //NOI18N - } else { - LOGGER.log(Level.WARNING, "no snapshot taken"); // NOI18N - } - } catch (Exception ex) { - Exceptions.printStackTrace(ex); - } - } - - } - -} +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2011 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]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2011 Sun + * Microsystems, Inc. All Rights Reserved. + * + * markiewb@netbeans.org + * + * 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. + */ + +package org.netbeans.modules.jumpto.type; + +import org.netbeans.spi.jumpto.type.SearchType; +import org.netbeans.spi.jumpto.type.TypeProvider; +import org.netbeans.spi.jumpto.type.TypeDescriptor; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import javax.swing.AbstractAction; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.ListCellRenderer; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JViewport; +import javax.swing.ListModel; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.api.jumpto.type.TypeBrowser; +import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.modules.jumpto.EntitiesListCellRenderer; +import org.netbeans.modules.jumpto.file.LazyListModel; +import org.netbeans.modules.sampler.Sampler; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.ErrorManager; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.nodes.Node; +import org.openide.text.NbDocument; +import org.openide.util.Exceptions; +import org.openide.util.HelpCtx; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; +import org.openide.windows.TopComponent; + +/** + * XXX split into action and support class, left this just to minimize diff + * XXX Icons + * XXX Don't look for all projects (do it lazy in filter or renderer) + * @author Petr Hrebejk + */ +public class GoToTypeAction extends AbstractAction implements GoToPanel.ContentProvider, LazyListModel.Filter { + + static final Logger LOGGER = Logger.getLogger(GoToTypeAction.class.getName()); // Used from the panel as well + + private SearchType nameKind; + private static ListModel EMPTY_LIST_MODEL = new DefaultListModel(); + private static final RequestProcessor rp = new RequestProcessor ("GoToTypeAction-RequestProcessor",1); //NOI18N + private static final RequestProcessor PROFILE_RP = new RequestProcessor("GoToTypeAction-Profile",1); //NOI18N + private Worker running; + private RequestProcessor.Task task; + GoToPanel panel; + private Dialog dialog; + private JButton okButton; + private Collection typeProviders; + private final Collection implicitTypeProviders; + private final TypeBrowser.Filter typeFilter; + private final String title; + private final boolean multiSelection; + + /** Creates a new instance of OpenTypeAction */ + public GoToTypeAction() { + this( + NbBundle.getMessage( GoToTypeAction.class, "DLG_GoToType" ), + null, + true + ); + } + + public GoToTypeAction(String title, TypeBrowser.Filter typeFilter, boolean multiSelection, TypeProvider... typeProviders) { + super( NbBundle.getMessage( GoToTypeAction.class,"TXT_GoToType") ); + putValue("PopupMenuText", NbBundle.getBundle(GoToTypeAction.class).getString("editor-popup-TXT_GoToType")); // NOI18N + this.title = title; + this.typeFilter = typeFilter; + this.implicitTypeProviders = typeProviders.length == 0 ? null : Collections.unmodifiableCollection(Arrays.asList(typeProviders)); + this.multiSelection = multiSelection; + } + + @Override + public void actionPerformed( ActionEvent e ) { + for (TypeDescriptor td : getSelectedTypes()) { + td.open(); + } + } + + public Iterable getSelectedTypes() { + return getSelectedTypes(true); + } + + public Iterable getSelectedTypes(final boolean visible) { + return getSelectedTypes(visible, null); + } + + public Iterable getSelectedTypes(final boolean visible, String initSearchText) { + Iterable result = Collections.emptyList(); + try { + panel = new GoToPanel(this, multiSelection); + dialog = createDialog(panel); + + if (initSearchText != null) { + panel.setInitialText(initSearchText); + } else { + Node[] arr = TopComponent.getRegistry ().getActivatedNodes(); + if (arr.length > 0) { + EditorCookie ec = arr[0].getCookie (EditorCookie.class); + if (ec != null) { + JEditorPane recentPane = NbDocument.findRecentEditorPane(ec); + if (recentPane != null) { + initSearchText = org.netbeans.editor.Utilities.getSelectionOrIdentifier(recentPane); + if (initSearchText != null && org.openide.util.Utilities.isJavaIdentifier(initSearchText)) { + panel.setInitialText(initSearchText); + } else { + panel.setInitialText(arr[0].getName()); + } + } + } + } + } + + dialog.setVisible(visible); + result = panel.getSelectedTypes(); + + } catch (IOException ex) { + ErrorManager.getDefault().notify(ex); + } + return result; + } + + @Override + public boolean isEnabled () { + return OpenProjects.getDefault().getOpenProjects().length>0; + } + + @Override + public boolean accept(Object obj) { + return typeFilter == null ? true : typeFilter.accept((TypeDescriptor) obj); + } + + @Override + public void scheduleUpdate(Runnable run) { + SwingUtilities.invokeLater(run); + } + + // Implementation of content provider -------------------------------------- + + + @Override + public ListCellRenderer getListCellRenderer(JList list, Document nameFieldDocument) { + + Renderer renderer = new Renderer(list); + //notify the render of the changed search text + nameFieldDocument.addDocumentListener(renderer); + + return renderer; + } + + + @Override + public void setListModel( GoToPanel panel, String text ) { + assert SwingUtilities.isEventDispatchThread(); + if (okButton != null) { + okButton.setEnabled (false); + } + //handling http://netbeans.org/bugzilla/show_bug.cgi?id=178555 + //add a MouseListener to the messageLabel JLabel so that the search can be cancelled without exiting the dialog + final GoToPanel goToPanel = panel; + final MouseListener warningMouseListener = new java.awt.event.MouseAdapter() { + + @Override + public void mouseClicked(java.awt.event.MouseEvent evt) { + if (running != null) { + running.cancel(); + task.cancel(); + running = null; + } + goToPanel.setListPanelContent(NbBundle.getMessage(GoToPanel.class, "TXT_SearchCanceled"),false); // NOI18N + } + }; + panel.setMouseListener(warningMouseListener); + if ( running != null ) { + running.cancel(); + task.cancel(); + running = null; + } + + if ( text == null ) { + panel.setModel(EMPTY_LIST_MODEL); + return; + } + + boolean exact = text.endsWith(" "); // NOI18N + + text = text.trim(); + + if ( text.length() == 0) { + panel.setModel(EMPTY_LIST_MODEL); + return; + } + + int wildcard = containsWildCard(text); + + if (exact) { + //nameKind = panel.isCaseSensitive() ? SearchType.EXACT_NAME : SearchType.CASE_INSENSITIVE_EXACT_NAME; + nameKind = SearchType.EXACT_NAME; + } + else if ((isAllUpper(text) && text.length() > 1) || isCamelCase(text)) { + nameKind = SearchType.CAMEL_CASE; + } + else if (wildcard != -1) { + nameKind = panel.isCaseSensitive() ? SearchType.REGEXP : SearchType.CASE_INSENSITIVE_REGEXP; + } + else { + nameKind = panel.isCaseSensitive() ? SearchType.PREFIX : SearchType.CASE_INSENSITIVE_PREFIX; + } + + // Compute in other thread + running = new Worker( text ); + task = rp.post( running, 220); + if ( panel.time != -1 ) { + LOGGER.log( Level.FINE, "Worker posted after {0} ms.", System.currentTimeMillis() - panel.time ); //NOI18N + } + } + + @Override + public void closeDialog() { + // Closing event can be sent several times. + if (dialog == null ) { // #172568 + return; // OK - the dialog has already been closed. + } + dialog.setVisible( false ); + cleanup(); + } + + @Override + public boolean hasValidContent () { + return this.okButton != null && this.okButton.isEnabled(); + } + + // Private methods --------------------------------------------------------- + + public static boolean isAllUpper( String text ) { + for( int i = 0; i < text.length(); i++ ) { + if ( !Character.isUpperCase( text.charAt( i ) ) ) { + return false; + } + } + + return true; + } + + public static int containsWildCard( String text ) { + for( int i = 0; i < text.length(); i++ ) { + if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N + return i; + } + } + return -1; + } + + private static Pattern camelCasePattern = Pattern.compile("(?:\\p{javaUpperCase}(?:\\p{javaLowerCase}|\\p{Digit}|\\.|\\$)*){2,}"); // NOI18N + + public static boolean isCamelCase(String text) { + return camelCasePattern.matcher(text).matches(); + } + + + /** Creates the dialog to show + */ + private Dialog createDialog( final GoToPanel panel) { + + okButton = new JButton (NbBundle.getMessage(GoToTypeAction.class, "CTL_OK")); + okButton.getAccessibleContext().setAccessibleDescription(okButton.getText()); + okButton.setEnabled (false); + panel.getAccessibleContext().setAccessibleName( NbBundle.getMessage( GoToTypeAction.class, "AN_GoToType") ); //NOI18N + panel.getAccessibleContext().setAccessibleDescription( NbBundle.getMessage( GoToTypeAction.class, "AD_GoToType") ); //NOI18N + + DialogDescriptor dialogDescriptor = new DialogDescriptor( + panel, // innerPane + title, // displayName + true, + new Object[] {okButton, DialogDescriptor.CANCEL_OPTION}, + okButton, + DialogDescriptor.DEFAULT_ALIGN, + HelpCtx.DEFAULT_HELP, + new DialogButtonListener( panel ) ); // Action listener + + dialogDescriptor.setClosingOptions(new Object[] {okButton, DialogDescriptor.CANCEL_OPTION}); + + // panel.addPropertyChangeListener( new HelpCtxChangeListener( dialogDescriptor, helpCtx ) ); +// if ( panel instanceof HelpCtx.Provider ) { +// dialogDescriptor.setHelpCtx( ((HelpCtx.Provider)panel).getHelpCtx() ); +// } + + Dialog d = DialogDisplayer.getDefault().createDialog( dialogDescriptor ); + + // Set size when needed + final int width = UiOptions.GoToTypeDialog.getWidth(); + final int height = UiOptions.GoToTypeDialog.getHeight(); + if (width != -1 && height != -1) { + d.setPreferredSize(new Dimension(width,height)); + } + + // Center the dialog after the size changed. + Rectangle r = Utilities.getUsableScreenBounds(); + int maxW = (r.width * 9) / 10; + int maxH = (r.height * 9) / 10; + final Dimension dim = d.getPreferredSize(); + dim.width = Math.min(dim.width, maxW); + dim.height = Math.min(dim.height, maxH); + d.setBounds(Utilities.findCenterBounds(dim)); + initialDimension = dim; + d.addWindowListener(new WindowAdapter() { + public @Override void windowClosed(WindowEvent e) { + cleanup(); + } + }); + + return d; + + } + + private Dimension initialDimension; + + private void cleanup() { + assert SwingUtilities.isEventDispatchThread(); + if ( GoToTypeAction.this.dialog != null ) { // Closing event for some reson sent twice + + // Save dialog size only when changed + final int currentWidth = dialog.getWidth(); + final int currentHeight = dialog.getHeight(); + if (initialDimension != null && (initialDimension.width != currentWidth || initialDimension.height != currentHeight)) { + UiOptions.GoToTypeDialog.setHeight(currentHeight); + UiOptions.GoToTypeDialog.setWidth(currentWidth); + } + initialDimension = null; + // Clean caches + GoToTypeAction.this.dialog.dispose(); + GoToTypeAction.this.dialog = null; + //1st) Cancel current task + if ( running != null ) { + running.cancel(); + task.cancel(); + running = null; + } + //2nd do clean up in the same thread as init to prevent races + rp.submit(new Runnable(){ + @Override + public void run() { + assert rp.isRequestProcessorThread(); + if (typeProviders != null) { + for (TypeProvider provider : typeProviders) { + provider.cleanup(); + } + typeProviders = null; + } + } + }); + } + } + + // Private classes --------------------------------------------------------- + + + + private class Worker implements Runnable { + + private volatile boolean isCanceled = false; + private volatile TypeProvider current; + private final String text; + + private final long createTime; + + public Worker( String text ) { + this.text = text; + this.createTime = System.currentTimeMillis(); + LOGGER.log( Level.FINE, "Worker for {0} - created after {1} ms.", //NOI18N + new Object[]{ + text, + System.currentTimeMillis() - panel.time + }); + } + + @Override + public void run() { + for (;;) { + final int[] retry = new int[1]; + + Profile profile = initializeProfiling(); + try { + LOGGER.log( Level.FINE, "Worker for {0} - started {1} ms.", //NOI18N + new Object[]{ + text, + System.currentTimeMillis() - createTime + }); + + final List types = getTypeNames(text, retry); + if ( isCanceled ) { + LOGGER.log( Level.FINE, "Worker for {0} exited after cancel {1} ms.", //NOI18N + new Object[]{ + text, + System.currentTimeMillis() - createTime + }); + return; + } + ListModel model = Models.fromList(types); + if (typeFilter != null) { + model = LazyListModel.create(model, GoToTypeAction.this, 0.1, "Not computed yet"); + } + final ListModel fmodel = model; + if ( isCanceled ) { + LOGGER.log( Level.FINE, "Worker for {0} exited after cancel {1} ms.", //NOI18N + new Object[]{ + text, + System.currentTimeMillis() - createTime + }); + return; + } + + if ( !isCanceled && fmodel != null ) { + LOGGER.log( Level.FINE, "Worker for text {0} finished after {1} ms.", //NOI18N + new Object[]{ + text, + System.currentTimeMillis() - createTime + }); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + panel.setModel(fmodel); + if (okButton != null && !types.isEmpty()) { + okButton.setEnabled (true); + } + } + }); + } + } finally { + if (profile != null) { + try { + profile.stop(); + } catch (Exception ex) { + LOGGER.log(Level.INFO, "Cannot stop profiling", ex); //NOI18N + } + } + } + + if (retry[0] > 0) { + try { + Thread.sleep(retry[0]); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + } else { + return; + } + } // for + } + + public void cancel() { + if ( panel.time != -1 ) { + LOGGER.log( Level.FINE, "Worker for text {0} canceled after {1} ms.", //NOI18N + new Object[]{ + text, + System.currentTimeMillis() - createTime + }); + } + TypeProvider _provider; + synchronized (this) { + isCanceled = true; + _provider = current; + } + if (_provider != null) { + _provider.cancel(); + } + } + + @SuppressWarnings("unchecked") + private List getTypeNames(String text, int[] retry) { + // TODO: Search twice, first for current project, then for all projects + List items; + // Multiple providers: merge results + items = new ArrayList(128); + String[] message = new String[1]; + TypeProvider.Context context = TypeProviderAccessor.DEFAULT.createContext(null, text, nameKind); + TypeProvider.Result result = TypeProviderAccessor.DEFAULT.createResult(items, message); + assert rp.isRequestProcessorThread(); + if (typeProviders == null) { + typeProviders = implicitTypeProviders != null ? implicitTypeProviders : Lookup.getDefault().lookupAll(TypeProvider.class); + } + for (TypeProvider provider : typeProviders) { + if (isCanceled) { + return null; + } + current = provider; + long start = System.currentTimeMillis(); + try { + LOGGER.log(Level.FINE, "Calling TypeProvider: {0}", provider); //NOI18N + provider.computeTypeNames(context, result); + } finally { + current = null; + } + long delta = System.currentTimeMillis() - start; + LOGGER.log(Level.FINE, "Provider ''{0}'' took {1} ms.", //NOI18N + new Object[]{ + provider.getDisplayName(), + delta + }); + } + retry[0] = TypeProviderAccessor.DEFAULT.getRetry(result); + if ( !isCanceled ) { + //time = System.currentTimeMillis(); + Collections.sort(items, new TypeComparator()); + panel.setWarning(message[0]); + //sort += System.currentTimeMillis() - time; + //LOGGER.fine("PERF - " + " GSS: " + gss + " GSB " + gsb + " CP: " + cp + " SFB: " + sfb + " GTN: " + gtn + " ADD: " + add + " SORT: " + sort ); + return items; + } + else { + return null; + } + } + } + + private static class MyPanel extends JPanel { + + private TypeDescriptor td; + + void setDescriptor(TypeDescriptor td) { + this.td = td; + // since the same component is reused for dirrerent list itens, + // null the tool tip + putClientProperty(TOOL_TIP_TEXT_KEY, null); + } + + @Override + public String getToolTipText() { + // the tool tip is gotten from the descriptor + // and cached in the standard TOOL_TIP_TEXT_KEY property + String text = (String) getClientProperty(TOOL_TIP_TEXT_KEY); + if( text == null ) { + if( td != null ) { + FileObject fo = td.getFileObject(); + if (fo != null) { + text = FileUtil.getFileDisplayName(fo); + } + } + putClientProperty(TOOL_TIP_TEXT_KEY, text); + } + return text; + } + } + + final void waitSearchFinished() { + assert SwingUtilities.isEventDispatchThread(); + task.waitFinished(); + } + + private static final class Renderer extends EntitiesListCellRenderer implements DocumentListener { + + private MyPanel rendererComponent; + private JLabel jlName = new JLabel(); + private JLabel jlPkg = new JLabel(); + private JLabel jlPrj = new JLabel(); + private int DARKER_COLOR_COMPONENT = 5; + private int LIGHTER_COLOR_COMPONENT = 80; + private Color fgColor; + private Color fgColorLighter; + private Color bgColor; + private Color bgColorDarker; + private Color bgSelectionColor; + private Color fgSelectionColor; + + private JList jList; + private String searchText = ""; + private final HighlightingTypeNameFormatter typeNameFormatter; + + @SuppressWarnings("LeakingThisInConstructor") + public Renderer( JList list ) { + + jList = list; + + Container container = list.getParent(); + if ( container instanceof JViewport ) { + ((JViewport)container).addChangeListener(this); + stateChanged(new ChangeEvent(container)); + } + + rendererComponent = new MyPanel(); + rendererComponent.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.anchor = GridBagConstraints.WEST; + c.insets = new Insets (0,0,0,7); + rendererComponent.add( jlName, c); + + c = new GridBagConstraints(); + c.gridx = 1; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.1; + c.anchor = GridBagConstraints.WEST; + c.insets = new Insets (0,0,0,7); + rendererComponent.add( jlPkg, c); + + c = new GridBagConstraints(); + c.gridx = 2; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.anchor = GridBagConstraints.EAST; + rendererComponent.add( jlPrj, c); + + + jlName.setOpaque(false); + jlPkg.setOpaque(false); + jlPrj.setOpaque(false); + + jlName.setFont(list.getFont()); + jlPkg.setFont(list.getFont()); + jlPrj.setFont(list.getFont()); + + + jlPrj.setHorizontalAlignment(RIGHT); + jlPrj.setHorizontalTextPosition(LEFT); + + // setFont( list.getFont() ); + fgColor = list.getForeground(); + fgColorLighter = new Color( + Math.min( 255, fgColor.getRed() + LIGHTER_COLOR_COMPONENT), + Math.min( 255, fgColor.getGreen() + LIGHTER_COLOR_COMPONENT), + Math.min( 255, fgColor.getBlue() + LIGHTER_COLOR_COMPONENT) + ); + + bgColor = list.getBackground(); + bgColorDarker = new Color( + Math.abs(bgColor.getRed() - DARKER_COLOR_COMPONENT), + Math.abs(bgColor.getGreen() - DARKER_COLOR_COMPONENT), + Math.abs(bgColor.getBlue() - DARKER_COLOR_COMPONENT) + ); + bgSelectionColor = list.getSelectionBackground(); + fgSelectionColor = list.getSelectionForeground(); + //TODO: get caseSensitive mode and init the highlighter + boolean caseSensitive=false; + this.typeNameFormatter = new HighlightingTypeNameFormatter(fgColor, bgColor, caseSensitive); + } + + public @Override Component getListCellRendererComponent( JList list, + Object value, + int index, + boolean isSelected, + boolean hasFocus) { + + // System.out.println("Renderer for index " + index ); + + int height = list.getFixedCellHeight(); + int width = list.getFixedCellWidth() - 1; + + width = width < 200 ? 200 : width; + + // System.out.println("w, h " + width + ", " + height ); + + Dimension size = new Dimension( width, height ); + rendererComponent.setMaximumSize(size); + rendererComponent.setPreferredSize(size); + + if ( isSelected ) { + jlName.setForeground(fgSelectionColor); + jlPkg.setForeground(fgSelectionColor); + jlPrj.setForeground(fgSelectionColor); + rendererComponent.setBackground(bgSelectionColor); + } + else { + jlName.setForeground(fgColor); + jlPkg.setForeground(fgColorLighter); + jlPrj.setForeground(fgColor); + rendererComponent.setBackground( index % 2 == 0 ? bgColor : bgColorDarker ); + } + + if ( value instanceof TypeDescriptor ) { + long time = System.currentTimeMillis(); + TypeDescriptor td = (TypeDescriptor)value; + jlName.setIcon(td.getIcon()); + + //highlight matching search text patterns in type + String typeName = td.getTypeName(); + String formattedTypeName = typeNameFormatter.formatTypeName(typeName, searchText); + jlName.setText(String.format("%s", formattedTypeName)); + jlPkg.setText(td.getContextName()); + setProjectName(jlPrj, td.getProjectName()); + jlPrj.setIcon(td.getProjectIcon()); + rendererComponent.setDescriptor(td); + LOGGER.log(Level.FINE, " Time in paint {0} ms.", System.currentTimeMillis() - time); //NOI18N + } + else { + jlName.setText( value.toString() ); + } + + return rendererComponent; + } + + @Override + public void stateChanged(ChangeEvent event) { + + JViewport jv = (JViewport)event.getSource(); + + jlName.setText( "Sample" ); // NOI18N + //jlName.setIcon(UiUtils.getElementIcon(ElementKind.CLASS, null)); + jlName.setIcon(ImageUtilities.loadImageIcon("org/netbeans/modules/jumpto/type/sample.png", false)); //NOI18N + + jList.setFixedCellHeight(jlName.getPreferredSize().height); + jList.setFixedCellWidth(jv.getExtentSize().width); + } + + @Override + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + + @Override + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + + @Override + public void changedUpdate(DocumentEvent e) { + + try { + String text = e.getDocument().getText(0, e.getDocument().getLength()); + searchText = text; + } catch (BadLocationException ex) { + searchText = ""; + } + } + } // Renderer + + private class DialogButtonListener implements ActionListener { + + private GoToPanel panel; + + public DialogButtonListener( GoToPanel panel ) { + this.panel = panel; + } + + @Override + public void actionPerformed(ActionEvent e) { + if ( e.getSource() == okButton) { + panel.setSelectedTypes(); + } + } + + } + + + private Profile initializeProfiling() { + boolean assertsOn = false; + assert assertsOn = true; + if (!assertsOn) { + return null; + } + + Sampler profiler = Sampler.createSampler("jumpto"); //NOI18N + if (profiler == null) { + return null; + } + return new Profile(profiler).start(); + } + + private class Profile implements Runnable { + private final long time; + private volatile Sampler profiler; + private volatile boolean profiling; + + public Profile(Sampler profiler) { + time = System.currentTimeMillis(); + this.profiler = profiler; + } + + Profile start() { + PROFILE_RP.post(this, 3000); // 3s + return this; + } + + @Override + public synchronized void run() { + if (profiler != null) { + profiling = true; + profiler.start(); + } + } + + private synchronized void stop() throws Exception { + long delta = System.currentTimeMillis() - time; + + Sampler ss = profiler; + profiler = null; + if (!profiling) { + return; + } + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(out); + ss.stopAndWriteTo(dos); + dos.close(); + if (dos.size() > 0) { + Object[] params = new Object[]{out.toByteArray(), delta, "GoToType" }; //NOI18N + Logger.getLogger("org.netbeans.ui.performance").log(Level.CONFIG, "Slowness detected", params); //NOI18N + } else { + LOGGER.log(Level.WARNING, "no snapshot taken"); // NOI18N + } + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + } + } + + } + +} diff --git a/jumpto/src/org/netbeans/modules/jumpto/type/HighlightingTypeNameFormatter.java b/jumpto/src/org/netbeans/modules/jumpto/type/HighlightingTypeNameFormatter.java new file mode 100644 --- /dev/null +++ b/jumpto/src/org/netbeans/modules/jumpto/type/HighlightingTypeNameFormatter.java @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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): markiewb@netbeans.org + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.jumpto.type; + +import java.awt.Color; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +/** + * Highlight the match of text patterns. The matching parts will be placed + * within HTML-tags, so it can be used easily within the Swing UI. + * @author markiewb + */ +class HighlightingTypeNameFormatter { + + private static final String FORMATPATTERN = "%s"; + private final boolean caseSensitive; + private String formatPattern; + + HighlightingTypeNameFormatter(Color bgColor, Color fgColor, boolean caseSensitive) { + String bgColorHighlight = Integer.toHexString(bgColor.getRGB()).substring(2); + String fgColorHighlight = Integer.toHexString(fgColor.getRGB()).substring(2); + formatPattern = String.format(FORMATPATTERN, bgColorHighlight, fgColorHighlight, "%s"); + this.caseSensitive = caseSensitive; + } + + List splitByCamelCaseAndWildcards(String searchText) { + //AbcDeFGhiJo -> [Abc, De, F, Ghi, Jo] + StringBuilder sb = new StringBuilder(searchText.length()); + for (char c : searchText.toCharArray()) { + if (Character.isUpperCase(c)) { + //add magic split marker into text before the uppercase char + //example: AbcDeFGhiJo -> &Abc&De&F&Ghi&Jo + sb.append("&"); + sb.append(c); + } else { + sb.append(c); + } + } + //split by camelcase (using the split marker) or the wildcards *,? + String[] split = sb.toString().split("[&|\\*|\\?]"); + return Arrays.asList(split); + } + + public String formatTypeName(String typeName, String textToFind) { + + if (null == textToFind || "".equals(textToFind)) { + return typeName; + } + BitSet bitSet = new BitSet(typeName.length()); + List parts = splitByCamelCaseAndWildcards(textToFind); + + String convertedTypeName = caseSensitive ? typeName : typeName.toLowerCase(); + //mark the chars to be highlighted + int startIndex = 0; + for (String camelCasePart : parts) { + + int indexOf = convertedTypeName.indexOf(caseSensitive ? camelCasePart : camelCasePart.toLowerCase(), startIndex); + if (indexOf != -1) { + + //mark the chars + bitSet.set(indexOf, indexOf + camelCasePart.length(), true); + } else { + break; + } + startIndex = indexOf + camelCasePart.length(); + } + + //highlight the marked chars via tags + StringBuilder formattedTypeName = new StringBuilder(); + int i = 0; + while (i < typeName.toCharArray().length) { + + boolean isMarked = bitSet.get(i); + + if (isMarked) { + int numberOfContinuousHighlights = bitSet.nextClearBit(i) - i; + String part = typeName.substring(i, i + numberOfContinuousHighlights); + formattedTypeName.append(String.format(formatPattern, part)); + i += numberOfContinuousHighlights; + } else { + formattedTypeName.append(typeName.charAt(i)); + i++; + } + } + return formattedTypeName.toString(); + } + + /** + * Allow to inject a pattern, so it can be tested easier. + * @param pattern + */ + void setFormatPattern(String pattern) { + this.formatPattern = pattern; + } +} diff --git a/jumpto/test/unit/src/org/netbeans/modules/jumpto/type/HighlightingTypeNameFormatterTest.java b/jumpto/test/unit/src/org/netbeans/modules/jumpto/type/HighlightingTypeNameFormatterTest.java new file mode 100644 --- /dev/null +++ b/jumpto/test/unit/src/org/netbeans/modules/jumpto/type/HighlightingTypeNameFormatterTest.java @@ -0,0 +1,115 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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): + * markiewb@netbeans.org + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.jumpto.type; + +import java.awt.Color; +import java.util.List; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; + +/** + * Tests if the pattern will be highlighted the correct way. + * @author markiewb + */ +public class HighlightingTypeNameFormatterTest { + + private Color fg = Color.WHITE; + private Color bg = Color.BLACK; + private HighlightingTypeNameFormatter cut; + + @Before + public void before() { + cut = new HighlightingTypeNameFormatter(fg, bg, true); + cut.setFormatPattern("[%s]"); + } + + @Test + public void testFormatTypeName_CamelCase() { + String typeName = "AbstractDummyBarTest"; + + assertEquals("[AbstractDummyBarTest]", cut.formatTypeName(typeName, "AbstractDummyBarTest")); + assertEquals("[Abstra]ct[D]ummy[B]arTest", cut.formatTypeName(typeName, "AbstraDB")); + assertEquals("[A]bstract[Dum]my[B]arTest", cut.formatTypeName(typeName, "ADumB")); + assertEquals("[A]bstract[D]ummy[Ba]rTest", cut.formatTypeName(typeName, "ADBa")); + assertEquals("[A]bstract[D]ummy[B]ar[Test]", cut.formatTypeName(typeName, "ADBTest")); + assertEquals("[Ab]stract[Du]mmy[B]ar[Test]", cut.formatTypeName(typeName, "AbDuBTest")); + } + + @Test + public void testFormatTypeName_NullOrEmpty() { + String typeName = "AbstractDummyBarTest"; + assertEquals("AbstractDummyBarTest", cut.formatTypeName(typeName, null)); + assertEquals("AbstractDummyBarTest", cut.formatTypeName(typeName, "")); + } + + @Test + public void testFormatTypeName_Wildcard_CaseSensitive() { + cut = new HighlightingTypeNameFormatter(fg, bg, true); + cut.setFormatPattern("[%s]"); + String typeName = "AbstractDummyBarTest"; + + assertEquals("[A]bstractDummyBar[Test]", cut.formatTypeName(typeName, "A*Test")); + assertEquals("[A]bstractDummy[B]ar[Test]", cut.formatTypeName(typeName, "A*B*Test")); + assertEquals("[A]bstractDummy[BarTest]", cut.formatTypeName(typeName, "A*Bar*Test")); + } + + @Test + public void testFormatTypeName_Wildcard_CaseInSensitive() { + cut = new HighlightingTypeNameFormatter(fg, bg, false); + cut.setFormatPattern("[%s]"); + String typeName = "AbstractDummyBarTest"; + + assertEquals("[A]bstractDummyBar[Test]", cut.formatTypeName(typeName, "A*Test")); + assertEquals("[Ab]stractDummyBar[Test]", cut.formatTypeName(typeName, "A*B*Test")); + assertEquals("[A]bstractDummy[BarTest]", cut.formatTypeName(typeName, "A*Bar*Test")); + } + + @Test + public void testFormatTypeName_FullFormat() { + cut = new HighlightingTypeNameFormatter(fg, bg, false); + String typeName = "AbstractDummyBarTest"; + + assertEquals("AbstractDummyBarTest", cut.formatTypeName(typeName, "ADBTest")); + } +}