0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.search;
0043:
0044: import java.awt.Color;
0045: import java.awt.Component;
0046: import java.awt.Dimension;
0047: import java.awt.GridLayout;
0048: import java.awt.ItemSelectable;
0049: import java.awt.SystemColor;
0050: import java.awt.event.FocusEvent;
0051: import java.awt.event.FocusListener;
0052: import java.awt.event.HierarchyEvent;
0053: import java.awt.event.HierarchyListener;
0054: import java.awt.event.ItemEvent;
0055: import java.awt.event.ItemListener;
0056: import java.awt.event.KeyEvent;
0057: import java.awt.event.KeyListener;
0058: import java.util.ArrayList;
0059: import java.util.Collection;
0060: import java.util.List;
0061: import java.util.Map;
0062: import java.util.logging.Logger;
0063: import javax.swing.AbstractButton;
0064: import javax.swing.BorderFactory;
0065: import javax.swing.ButtonGroup;
0066: import javax.swing.JButton;
0067: import javax.swing.JCheckBox;
0068: import javax.swing.JComboBox;
0069: import javax.swing.JComponent;
0070: import javax.swing.JLabel;
0071: import javax.swing.JPanel;
0072: import javax.swing.JRadioButton;
0073: import javax.swing.JRootPane;
0074: import javax.swing.SwingConstants;
0075: import javax.swing.SwingUtilities;
0076: import javax.swing.UIManager;
0077: import javax.swing.event.ChangeEvent;
0078: import javax.swing.event.ChangeListener;
0079: import javax.swing.event.DocumentEvent;
0080: import javax.swing.event.DocumentListener;
0081: import javax.swing.text.BadLocationException;
0082: import javax.swing.text.Document;
0083: import javax.swing.text.JTextComponent;
0084: import org.jdesktop.layout.GroupLayout;
0085: import org.jdesktop.layout.GroupLayout.ParallelGroup;
0086: import org.jdesktop.layout.GroupLayout.SequentialGroup;
0087: import org.jdesktop.layout.LayoutStyle;
0088: import org.openide.ErrorManager;
0089: import org.openide.awt.Mnemonics;
0090: import org.openide.util.Exceptions;
0091: import org.openide.util.NbBundle;
0092: import org.openidex.search.SearchHistory;
0093: import org.openidex.search.SearchPattern;
0094: import static java.awt.event.HierarchyEvent.DISPLAYABILITY_CHANGED;
0095: import static org.jdesktop.layout.GroupLayout.BASELINE;
0096: import static org.jdesktop.layout.GroupLayout.DEFAULT_SIZE;
0097: import static org.jdesktop.layout.GroupLayout.LEADING;
0098: import static org.jdesktop.layout.GroupLayout.PREFERRED_SIZE;
0099: import static org.jdesktop.layout.LayoutStyle.RELATED;
0100: import static org.jdesktop.layout.LayoutStyle.UNRELATED;
0101:
0102: /**
0103: *
0104: * @author Marian Petras
0105: */
0106: final class BasicSearchForm extends JPanel implements ChangeListener,
0107: KeyListener, ItemListener {
0108:
0109: private final BasicSearchCriteria searchCriteria;
0110: private final Map<SearchScope, Boolean> searchScopes;
0111: private final String preferredSearchScopeType;
0112: private SearchScope selectedSearchScope;
0113: private ChangeListener usabilityChangeListener;
0114:
0115: /** Creates new form BasicSearchForm */
0116: BasicSearchForm(Map<SearchScope, Boolean> searchScopes,
0117: String preferredSearchScopeType,
0118: BasicSearchCriteria criteria, boolean searchAndReplace,
0119: boolean usePreviousValues) {
0120: this .searchCriteria = (criteria != null) ? criteria
0121: : new BasicSearchCriteria();
0122: this .searchScopes = searchScopes;
0123: this .preferredSearchScopeType = preferredSearchScopeType;
0124: initComponents(searchAndReplace);
0125: initAccessibility();
0126: initHistory();
0127: initInteraction();
0128:
0129: if (searchAndReplace) {
0130: /* We must set the initial replace string, otherwise it might not
0131: * be initialized at all if the user keeps the field "Replace With:"
0132: * empty. One of the side-effects would be that method
0133: * BasicSearchCriteria.isSearchAndReplace() would return 'false'. */
0134: searchCriteria.setReplaceString(""); //NOI18N
0135: }
0136:
0137: /*
0138: * Interaction must be already set up when we set values, otherwise
0139: * state of the dialog might not be corresponding to the values,
0140: * e.g. the Find dialog could be disabled although valid values
0141: * are entered.
0142: */
0143: if (usePreviousValues) {
0144: initPreviousValues();
0145: } else {
0146: initValuesFromHistory();
0147: }
0148: }
0149:
0150: /** This method is called from within the constructor to
0151: * initialize the form.
0152: * WARNING: Do NOT modify this code. The content of this method is
0153: * always regenerated by the Form Editor.
0154: *
0155: * @param searchAndReplace {@code true} if components for
0156: * search & replace should be created;
0157: * {@code false} otherwise
0158: */
0159: private void initComponents(final boolean searchAndReplace) {
0160: JLabel lblTextToFind = new JLabel();
0161: cboxTextToFind = new JComboBox();
0162: lblTextToFind.setLabelFor(cboxTextToFind);
0163: lblHintTextToFind = new JLabel();
0164: lblHintTextToFind.setMinimumSize(new Dimension(0, 0));
0165:
0166: JLabel lblReplacement;
0167: JLabel lblDummyReplacement;
0168: if (searchAndReplace) {
0169: lblReplacement = new JLabel();
0170: cboxReplacement = new JComboBox();
0171: cboxReplacement
0172: .getAccessibleContext()
0173: .setAccessibleDescription(
0174: getText("BasicSearchForm.cbox.Replacement.AccessibleDescription"));
0175: lblReplacement.setLabelFor(cboxReplacement);
0176: lblDummyReplacement = new JLabel();
0177: } else {
0178: lblReplacement = null;
0179: cboxReplacement = null;
0180: lblDummyReplacement = null;
0181: }
0182:
0183: JLabel lblFileNamePattern = new JLabel();
0184: cboxFileNamePattern = new JComboBox();
0185: lblFileNamePattern.setLabelFor(cboxFileNamePattern);
0186: JLabel lblHintFileNamePattern = new JLabel();
0187:
0188: chkWholeWords = new JCheckBox();
0189: chkCaseSensitive = new JCheckBox();
0190: chkRegexp = new JCheckBox();
0191:
0192: Mnemonics.setLocalizedText(lblTextToFind,
0193: getText("BasicSearchForm.lblTextToFind.text")); //NOI18N
0194: lblHintTextToFind
0195: .setText(getText("BasicSearchForm.lblHintTextToFind.text")); //NOI18N
0196: lblHintTextToFind.setForeground(SystemColor.textInactiveText);
0197: cboxTextToFind.setEditable(true);
0198:
0199: if (searchAndReplace) {
0200: Mnemonics.setLocalizedText(lblReplacement,
0201: getText("BasicSearchForm.lblReplacement.text")); //NOI18N
0202: lblDummyReplacement.setText("dummy"); //NOI18N
0203: lblDummyReplacement
0204: .setForeground(SystemColor.textInactiveText);
0205: lblDummyReplacement.setVisible(false);
0206: cboxReplacement.setEditable(true);
0207: }
0208:
0209: Mnemonics.setLocalizedText(lblFileNamePattern,
0210: getText("BasicSearchForm.lblFileNamePattern.text")); //NOI18N
0211: lblHintFileNamePattern
0212: .setText(getText("BasicSearchForm.lblHintFileNamePattern.text"));//NOI18N
0213: lblHintFileNamePattern
0214: .setForeground(SystemColor.textInactiveText);
0215: cboxFileNamePattern.setEditable(true);
0216:
0217: Mnemonics.setLocalizedText(chkWholeWords,
0218: getText("BasicSearchForm.chkWholeWords.text")); //NOI18N
0219:
0220: Mnemonics.setLocalizedText(chkCaseSensitive,
0221: getText("BasicSearchForm.chkCaseSensitive.text")); //NOI18N
0222:
0223: Mnemonics.setLocalizedText(chkRegexp,
0224: getText("BasicSearchForm.chkRegexp.text")); //NOI18N
0225:
0226: JComponent optionsPanel = createButtonsPanel(
0227: "LBL_OptionsPanelTitle", //NOI18N
0228: chkWholeWords, chkCaseSensitive, chkRegexp);
0229: JComponent scopePanel = createButtonsPanel(
0230: "LBL_ScopePanelTitle", //NOI18N
0231: createSearchScopeButtons());
0232:
0233: GridLayout lowerPanelLayout = new GridLayout(1, 0);
0234: JComponent lowerPanel = new JPanel(lowerPanelLayout);
0235: lowerPanel.add(optionsPanel);
0236: lowerPanel.add(scopePanel);
0237: lowerPanelLayout.setHgap(LayoutStyle.getSharedInstance()
0238: .getPreferredGap(optionsPanel, scopePanel, UNRELATED,
0239: SwingConstants.EAST, null));
0240:
0241: GroupLayout criteriaPanelLayout = new GroupLayout(this );
0242: setLayout(criteriaPanelLayout);
0243: criteriaPanelLayout.setHonorsVisibility(false);
0244: criteriaPanelLayout
0245: .setHorizontalGroup(criteriaPanelLayout
0246: .createSequentialGroup()
0247: .addContainerGap()
0248: .add(
0249: criteriaPanelLayout
0250: .createParallelGroup(LEADING)
0251: .add(
0252: criteriaPanelLayout
0253: .createSequentialGroup()
0254: .add(
0255: createParallelGroup(
0256: criteriaPanelLayout,
0257: LEADING,
0258: lblTextToFind,
0259: lblReplacement,
0260: lblFileNamePattern))
0261: .addPreferredGap(
0262: RELATED)
0263: .add(
0264: createParallelGroup(
0265: criteriaPanelLayout,
0266: LEADING,
0267: lblHintFileNamePattern,
0268: lblDummyReplacement,
0269: lblHintTextToFind,
0270: cboxTextToFind,
0271: cboxReplacement,
0272: cboxFileNamePattern)))
0273: .add(lowerPanel))
0274: .addContainerGap());
0275:
0276: SequentialGroup seqGroup = criteriaPanelLayout
0277: .createSequentialGroup();
0278: seqGroup.addContainerGap().add(
0279: criteriaPanelLayout.createParallelGroup(BASELINE).add(
0280: lblTextToFind).add(cboxTextToFind,
0281: PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE))
0282: .addPreferredGap(RELATED).add(lblHintTextToFind);
0283: if (cboxReplacement != null) {
0284: seqGroup.addPreferredGap(UNRELATED).add(
0285: criteriaPanelLayout.createParallelGroup(BASELINE)
0286: .add(lblReplacement).add(cboxReplacement,
0287: PREFERRED_SIZE, DEFAULT_SIZE,
0288: PREFERRED_SIZE)).addPreferredGap(
0289: RELATED).add(lblDummyReplacement);
0290: }
0291: seqGroup.addPreferredGap(UNRELATED).add(
0292: criteriaPanelLayout.createParallelGroup(BASELINE).add(
0293: lblFileNamePattern).add(cboxFileNamePattern,
0294: PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE))
0295: .addPreferredGap(RELATED).add(lblHintFileNamePattern)
0296: .addPreferredGap(UNRELATED).add(lowerPanel,
0297: PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
0298: .addContainerGap();
0299: criteriaPanelLayout.setVerticalGroup(seqGroup);
0300:
0301: /* find the editor components of combo-boxes: */
0302: Component cboxEditorComp;
0303: cboxEditorComp = cboxTextToFind.getEditor()
0304: .getEditorComponent();
0305: textToFindEditor = (JTextComponent) cboxEditorComp;
0306: cboxEditorComp = cboxFileNamePattern.getEditor()
0307: .getEditorComponent();
0308: fileNamePatternEditor = (JTextComponent) cboxEditorComp;
0309: if (cboxReplacement != null) {
0310: cboxEditorComp = cboxReplacement.getEditor()
0311: .getEditorComponent();
0312: replacementPatternEditor = (JTextComponent) cboxEditorComp;
0313: }
0314: }
0315:
0316: /**
0317: * Creates a {@code ParallelGroup} and adds the given components to it,
0318: * skipping {@code null} components.
0319: * Calling
0320: * <blockquote><pre><code>createParallelGroup(groupLayout, alignment,
0321: * component1,
0322: * component2,
0323: * component3,
0324: * ...
0325: * component<i>n</i>)</code></pre></blockquote>
0326: * is equivalent to calling
0327: * <blockquote><pre><code>groupLayout.createParallelGroup(alignment)
0328: * .add(component1)
0329: * .add(component2)
0330: * .add(component3)
0331: * ...
0332: * .add(component<i>n</i>)</code></pre></blockquote>
0333: * except that {@code null} components are skipped and {@code JComboBox}
0334: * components are automatically added with size constraints
0335: * {@code (0, DEFAULT_SIZE, Short.MAX_VALUE)}.
0336: */
0337: private static ParallelGroup createParallelGroup(
0338: GroupLayout groupLayout, int alignment,
0339: Component... components) {
0340: ParallelGroup group = groupLayout
0341: .createParallelGroup(alignment);
0342: for (Component c : components) {
0343: if (c == null) {
0344: continue;
0345: }
0346:
0347: if (c.getClass() == JComboBox.class) {
0348: group.add(c, 0, DEFAULT_SIZE, Short.MAX_VALUE);
0349: } else {
0350: group.add(c);
0351: }
0352: }
0353: return group;
0354: }
0355:
0356: /**
0357: */
0358: private void initAccessibility() {
0359: chkCaseSensitive
0360: .getAccessibleContext()
0361: .setAccessibleDescription(
0362: getText("BasicSearchForm.chkCaseSensitive.AccessibleDescription"));
0363: chkRegexp
0364: .getAccessibleContext()
0365: .setAccessibleDescription(
0366: getText("BasicSearchForm.chkRegexp.AccessibleDescription"));
0367: chkWholeWords
0368: .getAccessibleContext()
0369: .setAccessibleDescription(
0370: getText("BasicSearchForm.chkWholeWords.AccessibleDescription"));
0371: }
0372:
0373: /**
0374: * Fills text and sets values of check-boxes according to the current
0375: * search criteria.
0376: */
0377: private void initPreviousValues() {
0378: String textPattern = searchCriteria.getTextPatternExpr();
0379: cboxTextToFind.setSelectedItem(textPattern);
0380: textToFindEditor.setText(textPattern);
0381:
0382: String fileNamePattern = searchCriteria
0383: .getFileNamePatternExpr();
0384: cboxFileNamePattern.setSelectedItem(fileNamePattern);
0385: fileNamePatternEditor.setText(fileNamePattern);
0386:
0387: if (cboxReplacement != null) {
0388: String replacementExpr = searchCriteria.getReplaceExpr();
0389: cboxReplacement.setSelectedItem(replacementExpr);
0390: replacementPatternEditor.setText(replacementExpr);
0391: }
0392:
0393: chkWholeWords.setSelected(searchCriteria.isWholeWords());
0394: chkCaseSensitive.setSelected(searchCriteria.isCaseSensitive());
0395: chkRegexp.setSelected(searchCriteria.isRegexp());
0396: }
0397:
0398: /**
0399: */
0400: private void initInteraction() {
0401: /* set up updating of the validity status: */
0402: class PatternChangeListener implements DocumentListener {
0403: private final JComboBox sourceComboBox;
0404:
0405: PatternChangeListener(JComboBox srcCBox) {
0406: this .sourceComboBox = srcCBox;
0407: }
0408:
0409: public void insertUpdate(DocumentEvent e) {
0410: update(e);
0411: }
0412:
0413: public void removeUpdate(DocumentEvent e) {
0414: update(e);
0415: }
0416:
0417: public void changedUpdate(DocumentEvent e) {
0418: update(e);
0419: }
0420:
0421: private void update(DocumentEvent e) {
0422: if ((sourceComboBox == cboxFileNamePattern)
0423: && ignoreFileNamePatternChanges) {
0424: return;
0425: }
0426:
0427: final Document doc = e.getDocument();
0428:
0429: String text;
0430: try {
0431: text = doc.getText(0, doc.getLength());
0432: } catch (BadLocationException ex) {
0433: assert false;
0434: ErrorManager.getDefault().notify(
0435: ErrorManager.ERROR, ex);
0436: text = ""; //NOI18N
0437: }
0438:
0439: if (sourceComboBox == cboxTextToFind) {
0440: searchCriteria.setTextPattern(text);
0441: updateTextPatternColor();
0442: } else if (sourceComboBox == cboxFileNamePattern) {
0443: searchCriteria.setFileNamePattern(text);
0444: } else {
0445: assert sourceComboBox == cboxReplacement;
0446: searchCriteria.setReplaceString(text);
0447: }
0448: }
0449: }
0450:
0451: final TextFieldFocusListener focusListener = new TextFieldFocusListener();
0452: textToFindEditor.addFocusListener(focusListener);
0453: if (replacementPatternEditor != null) {
0454: replacementPatternEditor.addFocusListener(focusListener);
0455: }
0456:
0457: final FileNamePatternWatcher watcher = new FileNamePatternWatcher(
0458: fileNamePatternEditor);
0459: fileNamePatternEditor.addFocusListener(watcher);
0460: fileNamePatternEditor.addHierarchyListener(watcher);
0461:
0462: textToFindEditor.getDocument().addDocumentListener(
0463: new PatternChangeListener(cboxTextToFind));
0464: fileNamePatternEditor.getDocument().addDocumentListener(
0465: new PatternChangeListener(cboxFileNamePattern));
0466: if (replacementPatternEditor != null) {
0467: replacementPatternEditor.getDocument().addDocumentListener(
0468: new PatternChangeListener(cboxReplacement));
0469: }
0470:
0471: textToFindEditor.addKeyListener(this );
0472: fileNamePatternEditor.addKeyListener(this );
0473: if (replacementPatternEditor != null) {
0474: replacementPatternEditor.addKeyListener(this );
0475: }
0476:
0477: chkRegexp.addItemListener(this );
0478: chkCaseSensitive.addItemListener(this );
0479: chkWholeWords.addItemListener(this );
0480:
0481: boolean regexp = chkRegexp.isSelected();
0482: chkCaseSensitive.setEnabled(!regexp);
0483: chkWholeWords.setEnabled(!regexp);
0484:
0485: searchCriteria.setUsabilityChangeListener(this );
0486: }
0487:
0488: /**
0489: * Initializes pop-ups of combo-boxes with last entered patterns and
0490: * expressions. The combo-boxes' text-fields remain empty.
0491: */
0492: private void initHistory() {
0493: final List<SearchPattern> patterns = SearchHistory.getDefault()
0494: .getSearchPatterns();
0495: if (!patterns.isEmpty()) {
0496: List<String> itemsList = new ArrayList<String>(patterns
0497: .size());
0498: for (SearchPattern pattern : patterns) {
0499: String searchExpression = pattern.getSearchExpression();
0500: if (!itemsList.contains(searchExpression)) {
0501: itemsList.add(searchExpression);
0502: }
0503: }
0504: cboxTextToFind.setModel(new ListComboBoxModel(itemsList));
0505: }
0506:
0507: FindDialogMemory memory = FindDialogMemory.getDefault();
0508: List<String> entries;
0509:
0510: entries = memory.getFileNamePatterns();
0511: if (!entries.isEmpty()) {
0512: cboxFileNamePattern.setModel(new ListComboBoxModel(entries,
0513: true));
0514: }
0515:
0516: if (cboxReplacement != null) {
0517: entries = memory.getReplacementExpressions();
0518: if (!entries.isEmpty()) {
0519: cboxReplacement.setModel(new ListComboBoxModel(entries,
0520: true));
0521: }
0522: }
0523: }
0524:
0525: /**
0526: */
0527: private void initValuesFromHistory() {
0528: final FindDialogMemory memory = FindDialogMemory.getDefault();
0529:
0530: if (memory.isTextPatternSpecified()
0531: && (cboxTextToFind.getItemCount() != 0)) {
0532: cboxTextToFind.setSelectedIndex(0);
0533: textToFindEditor.setText(cboxTextToFind.getSelectedItem()
0534: .toString());
0535: }
0536: if (memory.isFileNamePatternSpecified()
0537: && cboxFileNamePattern.getItemCount() != 0) {
0538: cboxFileNamePattern.setSelectedIndex(0);
0539: fileNamePatternEditor.setText(cboxFileNamePattern
0540: .getSelectedItem().toString());
0541: }
0542: if (cboxReplacement != null
0543: && cboxReplacement.getItemCount() != 0) {
0544: cboxReplacement.setSelectedIndex(0);
0545: replacementPatternEditor.setText(cboxReplacement
0546: .getSelectedItem().toString());
0547: }
0548:
0549: chkWholeWords.setSelected(memory.isWholeWords());
0550: chkCaseSensitive.setSelected(memory.isCaseSensitive());
0551: chkRegexp.setSelected(memory.isRegularExpression());
0552: }
0553:
0554: @Override
0555: public boolean requestFocusInWindow() {
0556: assert textToFindEditor != null;
0557:
0558: if (textToFindEditor.isFocusOwner()) {
0559: return true;
0560: }
0561:
0562: int textLength = textToFindEditor.getText().length();
0563: if (textLength > 0) {
0564: textToFindEditor.setCaretPosition(0);
0565: textToFindEditor.moveCaretPosition(textLength);
0566: }
0567:
0568: return textToFindEditor.requestFocusInWindow();
0569: }
0570:
0571: /**
0572: * Sets proper color of text pattern.
0573: */
0574: private void updateTextPatternColor() {
0575: boolean wasInvalid = invalidTextPattern;
0576: invalidTextPattern = searchCriteria.isTextPatternInvalid();
0577: if (invalidTextPattern != wasInvalid) {
0578: if (defaultTextColor == null) {
0579: assert !wasInvalid;
0580: defaultTextColor = textToFindEditor.getForeground();
0581: }
0582: textToFindEditor
0583: .setForeground(invalidTextPattern ? getErrorTextColor()
0584: : defaultTextColor);
0585: }
0586: }
0587:
0588: private Color getErrorTextColor() {
0589: if (errorTextColor == null) {
0590: errorTextColor = UIManager.getDefaults().getColor(
0591: "TextField.errorForeground"); //NOI18N
0592: if (errorTextColor == null) {
0593: errorTextColor = Color.RED;
0594: }
0595: }
0596: return errorTextColor;
0597: }
0598:
0599: void setUsabilityChangeListener(ChangeListener l) {
0600: usabilityChangeListener = l;
0601: }
0602:
0603: public void stateChanged(ChangeEvent e) {
0604: if (usabilityChangeListener != null) {
0605: usabilityChangeListener.stateChanged(new ChangeEvent(this ));
0606: }
0607: }
0608:
0609: /**
0610: * Called when some of the check-boxes is selected or deselected.
0611: *
0612: * @param e event object holding information about the change
0613: */
0614: public void itemStateChanged(ItemEvent e) {
0615: final ItemSelectable toggle = e.getItemSelectable();
0616: final boolean selected = (e.getStateChange() == ItemEvent.SELECTED);
0617: if (toggle == chkRegexp) {
0618: searchCriteria.setRegexp(selected);
0619: updateTextPatternColor();
0620: chkCaseSensitive.setEnabled(!selected);
0621: chkWholeWords.setEnabled(!selected);
0622: lblHintTextToFind.setVisible(!selected);
0623: } else if (toggle == chkCaseSensitive) {
0624: searchCriteria.setCaseSensitive(selected);
0625: } else if (toggle == chkWholeWords) {
0626: searchCriteria.setWholeWords(selected);
0627: } else {
0628: assert false;
0629: }
0630: }
0631:
0632: /**
0633: * If the pressed key was Enter, activates the default button (if enabled).
0634: * It also consumes the event so that the default
0635: * {@code JTextField}'s handling mechanism is bypassed.
0636: */
0637: public void keyPressed(KeyEvent e) {
0638: if ((e.getKeyCode() == KeyEvent.VK_ENTER)
0639: && (e.getModifiersEx() == 0)) {
0640:
0641: JRootPane rootPane = SwingUtilities.getRootPane(this );
0642: if (rootPane != null) {
0643: JButton button = rootPane.getDefaultButton();
0644: if ((button != null) && button.isEnabled()) {
0645: e.consume();
0646: button.doClick();
0647: }
0648: }
0649: }
0650: }
0651:
0652: public void keyReleased(KeyEvent e) {
0653: }
0654:
0655: public void keyTyped(KeyEvent e) {
0656: }
0657:
0658: /**
0659: */
0660: private AbstractButton[] createSearchScopeButtons() {
0661: radioBtnGroup = new ButtonGroup();
0662:
0663: ItemListener buttonStateListener = new ItemListener() {
0664: public void itemStateChanged(ItemEvent e) {
0665: if (e.getStateChange() == ItemEvent.SELECTED) {
0666: AbstractButton selectedButton = (AbstractButton) e
0667: .getSource();
0668: Object storedObject = selectedButton
0669: .getClientProperty("searchScope");
0670: selectedSearchScope = (SearchScope) storedObject;
0671: }
0672: }
0673: };
0674:
0675: AbstractButton[] result = new AbstractButton[searchScopes
0676: .size()];
0677: int index = 0;
0678:
0679: boolean preferredScopeSelected = false;
0680: int firstEnabled = -1;
0681: for (Map.Entry<SearchScope, Boolean> entry : orderSearchScopes()) {
0682: SearchScope searchScope = entry.getKey();
0683: boolean enabled = entry.getValue();
0684: String searchScopeInfo = enabled ? searchScope
0685: .getAdditionalInfo() : null;
0686: AbstractButton button = (searchScopeInfo == null) ? new JRadioButton()
0687: : new ButtonWithExtraInfo(searchScopeInfo);
0688: Mnemonics.setLocalizedText(button, searchScope
0689: .getDisplayName());
0690: button.getAccessibleContext().setAccessibleDescription(
0691: searchScope.getDisplayName());
0692: button.putClientProperty("searchScope", searchScope);
0693: button.addItemListener(buttonStateListener);
0694:
0695: button.setEnabled(enabled);
0696: if (enabled) {
0697: if (searchScope.getTypeId().equals(
0698: preferredSearchScopeType)) {
0699: button.setSelected(true);
0700: preferredScopeSelected = true;
0701: } else if (firstEnabled == -1) {
0702: firstEnabled = index;
0703: }
0704: }
0705: result[index++] = button;
0706:
0707: radioBtnGroup.add(button);
0708: }
0709:
0710: if (!preferredScopeSelected && (firstEnabled != -1)) {
0711: result[firstEnabled].setSelected(true);
0712: }
0713: return result;
0714: }
0715:
0716: /**
0717: * Moves the node selection search scope to the last position.
0718: * The implementation assumes that the node selection search scope
0719: * is the first search scope among all registered search scopes.
0720: */
0721: private Collection<Map.Entry<SearchScope, Boolean>> orderSearchScopes() {
0722: Collection<Map.Entry<SearchScope, Boolean>> currentCollection = searchScopes
0723: .entrySet();
0724:
0725: if (currentCollection.isEmpty()
0726: || (currentCollection.size() == 1)) {
0727: return currentCollection;
0728: }
0729:
0730: Collection<Map.Entry<SearchScope, Boolean>> newCollection = new ArrayList<Map.Entry<SearchScope, Boolean>>(
0731: currentCollection.size());
0732: Map.Entry<SearchScope, Boolean> firstEntry = null;
0733: for (Map.Entry<SearchScope, Boolean> entry : currentCollection) {
0734: if (firstEntry == null) {
0735: firstEntry = entry;
0736: } else {
0737: newCollection.add(entry);
0738: }
0739: }
0740: newCollection.add(firstEntry);
0741: return newCollection;
0742: }
0743:
0744: /**
0745: */
0746: private JComponent createButtonsPanel(String borderTitleBundleKey,
0747: AbstractButton... buttons) {
0748: JComponent buttonsPanel = new JPanel();
0749: GroupLayout buttonsPanelLayout = new GroupLayout(buttonsPanel);
0750: buttonsPanel.setLayout(buttonsPanelLayout);
0751:
0752: ParallelGroup parallelGroup = buttonsPanelLayout
0753: .createParallelGroup();
0754: for (AbstractButton button : buttons) {
0755: if (button instanceof ButtonWithExtraInfo) {
0756: /*
0757: * parallelGroup.add(button) makes the button's maximum size
0758: * equal to its preferred size. We need the button to expand
0759: * horizontally so we set its maximum width to MAX_VALUE.
0760: * If horizontal expanding is set on a button, it causes
0761: * that the button passes the invalidate-validate cycle much
0762: * more frequently - so we only set it on buttons which need it
0763: * (i.e. on buttons with extra information available).
0764: */
0765: parallelGroup.add(button, GroupLayout.DEFAULT_SIZE,
0766: GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE);
0767: } else {
0768: parallelGroup.add(button);
0769: }
0770: }
0771: buttonsPanelLayout.setHorizontalGroup(parallelGroup);
0772:
0773: GroupLayout.SequentialGroup sequentialGroup = buttonsPanelLayout
0774: .createSequentialGroup();
0775: boolean first = true;
0776: for (AbstractButton button : buttons) {
0777: if (!first) {
0778: sequentialGroup.addPreferredGap(RELATED);
0779: }
0780: sequentialGroup.add(button);
0781: first = false;
0782: }
0783: buttonsPanelLayout.setVerticalGroup(sequentialGroup);
0784:
0785: buttonsPanelLayout.linkSize(buttons, GroupLayout.VERTICAL);
0786:
0787: buttonsPanel
0788: .setBorder(BorderFactory
0789: .createCompoundBorder(
0790: BorderFactory
0791: .createTitledBorder(getText(borderTitleBundleKey)),
0792: BorderFactory.createEmptyBorder(3, 5,
0793: 5, 5)));
0794:
0795: return buttonsPanel;
0796: }
0797:
0798: /**
0799: * Called when the criteria in the Find dialog are confirmed by the user
0800: * and the search is about to be started.
0801: * This method just passes the message to the criteria object.
0802: */
0803: void onOk() {
0804: searchCriteria.onOk();
0805:
0806: final FindDialogMemory memory = FindDialogMemory.getDefault();
0807:
0808: if (searchCriteria.isTextPatternUsable()) {
0809: SearchHistory.getDefault().add(getCurrentSearchPattern());
0810: memory.setTextPatternSpecified(true);
0811: } else {
0812: memory.setTextPatternSpecified(false);
0813: }
0814: if (searchCriteria.isFileNamePatternUsable()) {
0815: memory
0816: .storeFileNamePattern(fileNamePatternEditor
0817: .getText());
0818: memory.setFileNamePatternSpecified(true);
0819: } else {
0820: memory.setFileNamePatternSpecified(false);
0821: }
0822: if (replacementPatternEditor != null) {
0823: memory.storeReplacementExpression(replacementPatternEditor
0824: .getText());
0825: }
0826: memory.setWholeWords(chkWholeWords.isSelected());
0827: memory.setCaseSensitive(chkCaseSensitive.isSelected());
0828: memory.setRegularExpression(chkRegexp.isSelected());
0829: }
0830:
0831: /**
0832: * Read current dialog contents as a SearchPattern.
0833: * @return SearchPattern for the contents of the current dialog. Null if the * search string is empty, meaning that the dialog is empty.
0834: */
0835: private SearchPattern getCurrentSearchPattern() {
0836: return SearchPattern.create(textToFindEditor.getText(),
0837: chkWholeWords.isSelected(), chkCaseSensitive
0838: .isSelected(), chkRegexp.isSelected());
0839: }
0840:
0841: /**
0842: */
0843: SearchScope getSelectedSearchScope() {
0844: assert selectedSearchScope != null;
0845: return selectedSearchScope;
0846: }
0847:
0848: /** */
0849: BasicSearchCriteria getBasicSearchCriteria() {
0850: return searchCriteria;
0851: }
0852:
0853: boolean isUsable() {
0854: return (selectedSearchScope != null)
0855: && searchCriteria.isUsable();
0856: }
0857:
0858: /**
0859: * Listener that selects all text in a text field when the text field
0860: * gains permanent focus.
0861: */
0862: private static class TextFieldFocusListener implements
0863: FocusListener {
0864:
0865: public void focusGained(FocusEvent e) {
0866: if (!e.isTemporary()) {
0867: JTextComponent textComp = (JTextComponent) e
0868: .getSource();
0869: if (textComp.getText().length() != 0) {
0870: textComp.selectAll();
0871: }
0872: }
0873: }
0874:
0875: public void focusLost(FocusEvent e) {
0876: /* do nothing */
0877: }
0878:
0879: }
0880:
0881: /**
0882: * Extension of the {@code TextFieldFocusListener}
0883: * - besides selecting of all text upon focus gain,
0884: * it displays "(no files)" if no file name pattern is specified.
0885: *
0886: * @author Marian Petras
0887: */
0888: private final class FileNamePatternWatcher extends
0889: TextFieldFocusListener implements HierarchyListener {
0890:
0891: private final Logger watcherLogger = Logger
0892: .getLogger("org.netbeans.modules.search.BasicSearchForm.FileNamePatternWatcher");//NOI18N
0893:
0894: private final JTextComponent txtComp;
0895: private final Document doc;
0896:
0897: private Color foregroundColor;
0898: private String infoText;
0899: private boolean infoDisplayed;
0900:
0901: private FileNamePatternWatcher(JTextComponent txtComp) {
0902: this .txtComp = txtComp;
0903: doc = txtComp.getDocument();
0904: }
0905:
0906: public void hierarchyChanged(HierarchyEvent e) {
0907: if ((e.getComponent() != txtComp)
0908: || ((e.getChangeFlags() & DISPLAYABILITY_CHANGED) == 0)
0909: || !txtComp.isDisplayable()) {
0910: return;
0911: }
0912:
0913: watcherLogger.finer("componentShown()");
0914: if (foregroundColor == null) {
0915: foregroundColor = txtComp.getForeground();
0916: }
0917: if ((doc.getLength() == 0) && !txtComp.isFocusOwner()) {
0918: displayInfo();
0919: }
0920: }
0921:
0922: @Override
0923: public void focusGained(FocusEvent e) {
0924:
0925: /*
0926: * Order of method calls hideInfo() and super.focusGained(e)
0927: * is important! See bug #113202.
0928: */
0929:
0930: if (infoDisplayed) {
0931: hideInfo();
0932: }
0933: super .focusGained(e); //selects all text
0934: }
0935:
0936: @Override
0937: public void focusLost(FocusEvent e) {
0938: super .focusLost(e); //does nothing
0939: if (isEmptyText()) {
0940: displayInfo();
0941: }
0942: }
0943:
0944: private boolean isEmptyText() {
0945: int length = doc.getLength();
0946: if (length == 0) {
0947: return true;
0948: }
0949:
0950: String text;
0951: try {
0952: text = doc.getText(0, length);
0953: } catch (Exception ex) {
0954: Exceptions.printStackTrace(ex);
0955: text = null;
0956: }
0957: return (text != null) && (text.trim().length() == 0);
0958: }
0959:
0960: private void displayInfo() {
0961: assert ((doc.getLength() == 0) && !txtComp.isFocusOwner());
0962: watcherLogger.finer("displayInfo()");
0963:
0964: try {
0965: txtComp.setForeground(txtComp.getDisabledTextColor());
0966:
0967: ignoreFileNamePatternChanges = true;
0968: doc.insertString(0, getInfoText(), null);
0969: } catch (BadLocationException ex) {
0970: Exceptions.printStackTrace(ex);
0971: } finally {
0972: ignoreFileNamePatternChanges = false;
0973: infoDisplayed = true;
0974: }
0975: }
0976:
0977: private void hideInfo() {
0978: watcherLogger.finer("hideInfo()");
0979:
0980: txtComp.setEnabled(true);
0981: try {
0982: ignoreFileNamePatternChanges = true;
0983: doc.remove(0, doc.getLength());
0984: } catch (BadLocationException ex) {
0985: Exceptions.printStackTrace(ex);
0986: } finally {
0987: ignoreFileNamePatternChanges = false;
0988: txtComp.setForeground(foregroundColor);
0989: infoDisplayed = false;
0990: }
0991: }
0992:
0993: private String getInfoText() {
0994: if (infoText == null) {
0995: infoText = NbBundle.getMessage(getClass(),
0996: "BasicSearchForm.cboxFileNamePattern.allFiles"); //NOI18N
0997: }
0998: return infoText;
0999: }
1000:
1001: }
1002:
1003: private String getText(String bundleKey) {
1004: return NbBundle.getMessage(getClass(), bundleKey);
1005: }
1006:
1007: private ButtonGroup radioBtnGroup;
1008: private JComboBox cboxTextToFind;
1009: private JComboBox cboxReplacement;
1010: private JComboBox cboxFileNamePattern;
1011: private JCheckBox chkWholeWords;
1012: private JCheckBox chkCaseSensitive;
1013: private JCheckBox chkRegexp;
1014: private JTextComponent textToFindEditor;
1015: private JTextComponent fileNamePatternEditor;
1016: private JTextComponent replacementPatternEditor;
1017: private JLabel lblHintTextToFind;
1018:
1019: private Color errorTextColor, defaultTextColor;
1020: private boolean invalidTextPattern = false;
1021:
1022: /**
1023: * When set to {@link true}, changes of file name pattern are ignored.
1024: * This is needed when the text in the file name pattern is programatically
1025: * (i.e. not by the user) set to "(all files)" and when this text is
1026: * cleared (when the text field gets focus).
1027: */
1028: private boolean ignoreFileNamePatternChanges = false;
1029:
1030: }
|