001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.search;
043:
044: import java.awt.Component;
045: import java.awt.Dialog;
046: import java.awt.GridLayout;
047: import java.awt.event.ActionEvent;
048: import java.awt.event.ActionListener;
049: import java.awt.event.FocusEvent;
050: import java.awt.event.FocusListener;
051: import java.beans.PropertyChangeEvent;
052: import java.beans.PropertyChangeListener;
053: import java.util.ArrayList;
054: import java.util.Collection;
055: import java.util.Collections;
056: import java.util.HashSet;
057: import java.util.List;
058: import java.util.Map;
059: import java.util.Set;
060: import javax.swing.JButton;
061: import javax.swing.JPanel;
062: import javax.swing.JTabbedPane;
063: import javax.swing.event.ChangeEvent;
064: import javax.swing.event.ChangeListener;
065: import org.openide.DialogDescriptor;
066: import org.openide.DialogDisplayer;
067: import org.openide.awt.Mnemonics;
068: import org.openide.util.HelpCtx;
069: import org.openide.util.NbBundle;
070: import org.openidex.search.SearchType;
071:
072: /**
073: * Panel which shows all enabled search types for user allowing them to
074: * select appropriate criteria for a new search.
075: *
076: * @author Peter Zavadsky
077: * @author Marian Petras
078: * @see SearchTypePanel
079: */
080: public final class SearchPanel extends JPanel implements
081: PropertyChangeListener, FocusListener, ChangeListener,
082: ActionListener {
083:
084: /** Return status code - returned if Cancel button has been pressed. */
085: public static final int RET_CANCEL = 0;
086:
087: /** Return status code - returned if OK button has been pressed. */
088: public static final int RET_OK = 1;
089:
090: /** */
091: private final BasicSearchForm basicCriteriaPanel;
092:
093: /** */
094: private final boolean projectWide;
095: /** */
096: private final boolean searchAndReplace;
097:
098: /** OK button. */
099: private final JButton okButton;
100:
101: /** Cancel button. */
102: private final JButton cancelButton;
103:
104: /** Java equivalent. */
105: private Dialog dialog;
106:
107: /** Return status. */
108: private int returnStatus = RET_CANCEL;
109:
110: /** Ordered list of <code>SearchTypePanel</code>'s. */
111: private List<SearchTypePanel> orderedSearchTypePanels;
112:
113: /**
114: * Creates a new {@code SearchPanel}.
115: *
116: * @param basicSearchCriteria basic search criteria,
117: * or {@code null} if basic search criteria
118: * should not be used
119: * @param preferredSearchScope preferred search scope (may be {@code null})
120: */
121: SearchPanel(Map<SearchScope, Boolean> searchScopes,
122: String preferredSearchScopeType, boolean searchAndReplace) {
123: this (searchScopes, preferredSearchScopeType, null, Utils
124: .cloneSearchTypes(Utils.getSearchTypes()), false,
125: searchAndReplace);
126: }
127:
128: /**
129: * Creates a new {@code SearchPanel}.
130: *
131: * @param basicSearchCriteria basic search criteria,
132: * or {@code null} if basic search criteria
133: * should not be used
134: * @param extraSearchTypes list of extra {@code SearchType}s to use
135: */
136: SearchPanel(Map<SearchScope, Boolean> searchScopes,
137: String preferredSearchScopeType,
138: BasicSearchCriteria basicSearchCriteria,
139: Collection<? extends SearchType> extraSearchTypes) {
140: this (searchScopes, preferredSearchScopeType,
141: basicSearchCriteria, extraSearchTypes, true,
142: basicSearchCriteria.isSearchAndReplace());
143: }
144:
145: private SearchPanel(
146: Map<SearchScope, Boolean> searchScopes,
147: String preferredSearchScopeType, //may be null
148: BasicSearchCriteria basicSearchCriteria, //may be null
149: Collection<? extends SearchType> extraSearchTypes,
150: boolean activateWithPreviousValues, boolean searchAndReplace) {
151: assert (extraSearchTypes != null);
152: if (extraSearchTypes == null) {
153: extraSearchTypes = Collections.<SearchType> emptyList();
154: }
155:
156: projectWide = SearchScopeRegistry
157: .hasProjectSearchScopes(searchScopes.keySet());
158: this .searchAndReplace = searchAndReplace;
159:
160: /* Create panel for entering basic search criteria: */
161: basicCriteriaPanel = new BasicSearchForm(searchScopes,
162: preferredSearchScopeType, basicSearchCriteria,
163: searchAndReplace, activateWithPreviousValues);
164: basicCriteriaPanel.setUsabilityChangeListener(this );
165:
166: /* Create search type panels: */
167: setLayout(new GridLayout(1, 1));
168: if (!extraSearchTypes.isEmpty()) {
169:
170: orderedSearchTypePanels = new ArrayList<SearchTypePanel>(
171: extraSearchTypes.size());
172: tabbedPane = new JTabbedPane();
173: tabbedPane.add(basicCriteriaPanel);
174:
175: Set<String> processedClassNames = new HashSet<String>();
176: for (SearchType searchType : extraSearchTypes) {
177: String className = searchType.getClass().getName();
178: if (!processedClassNames.add(className)) {
179: continue;
180: }
181:
182: SearchTypePanel newPanel = new SearchTypePanel(
183: searchType);
184: int index = orderedSearchTypePanels.indexOf(newPanel);
185: if (index != -1) {
186: continue;
187: }
188:
189: orderedSearchTypePanels.add(newPanel);
190: newPanel.addPropertyChangeListener(this );
191:
192: tabbedPane.add(newPanel);
193: }
194:
195: add(tabbedPane);
196:
197: // initial selection
198: int tabIndex = 0; //prevents bug #43843 ("AIOOBE after push button Modify Search")
199: /*
200: * we will use activateWithPreviousValues for the decision
201: * whether to pre-select the last selected tab
202: * (with the last used SearchType) or not.
203: */
204: if (activateWithPreviousValues) {
205: int searchTypeIndex = getIndexOfSearchType(FindDialogMemory
206: .getDefault().getLastSearchType());
207: tabIndex = searchTypeIndex + 1;
208: /* if searchTypeIndex is -1, then tabIndex is 0 */
209: }
210: tabbedPane.setSelectedIndex(tabIndex);
211: updateFirstTabText();
212: updateExtraTabsTexts();
213: } else {
214: orderedSearchTypePanels = null;
215: tabbedPane = null;
216:
217: add(basicCriteriaPanel);
218: }
219:
220: setName(NbBundle.getMessage(SearchPanel.class,
221: "TEXT_TITLE_CUSTOMIZE")); //NOI18N
222:
223: okButton = new JButton(NbBundle.getMessage(SearchPanel.class,
224: "TEXT_BUTTON_SEARCH")); //NOI18N
225: updateIsCustomized();
226:
227: Mnemonics.setLocalizedText(cancelButton = new JButton(),
228: NbBundle.getMessage(SearchPanel.class,
229: "TEXT_BUTTON_CANCEL")); //NOI18N
230:
231: initAccessibility();
232: }
233:
234: /**
235: * This method is called when the Search or Close button is pressed.
236: * It closes the Find dialog, cleans up the individual panels
237: * and sets the return status.
238: *
239: * @see #getReturnStatus
240: */
241: public void actionPerformed(final ActionEvent evt) {
242: doClose(evt.getSource() == okButton ? RET_OK : RET_CANCEL);
243: }
244:
245: SearchScope getSearchScope() {
246: return basicCriteriaPanel.getSelectedSearchScope();
247: }
248:
249: /**
250: * Returns basic criteria entered in the Find dialog.
251: *
252: * @return basic criteria specified in the Find dialog, or {@code null}
253: * if no basic criteria are specified or if the criteria
254: * are not valid (e.g. if an invalid search pattern is specified)
255: */
256: BasicSearchCriteria getBasicSearchCriteria() {
257: BasicSearchCriteria basicCriteria = basicCriteriaPanel
258: .getBasicSearchCriteria();
259: return basicCriteria.isUsable() ? basicCriteria : null;
260: }
261:
262: /**
263: */
264: List<SearchType> getSearchTypes() {
265: List<SearchType> result;
266: if (orderedSearchTypePanels == null) {
267: result = Collections.<SearchType> emptyList();
268: } else {
269: result = new ArrayList<SearchType>(orderedSearchTypePanels
270: .size());
271: for (SearchTypePanel searchTypePanel : orderedSearchTypePanels) {
272: result.add(searchTypePanel.getSearchType());
273: }
274: }
275: return result;
276: }
277:
278: private void initAccessibility() {
279: this .getAccessibleContext().setAccessibleDescription(
280: NbBundle.getBundle(SearchPanel.class).getString(
281: "ACS_SearchPanel")); // NOI18N
282: if (tabbedPane != null) {
283: tabbedPane.getAccessibleContext().setAccessibleName(
284: NbBundle.getBundle(SearchPanel.class).getString(
285: "ACSN_Tabs")); // NOI18N
286: tabbedPane.getAccessibleContext().setAccessibleDescription(
287: NbBundle.getBundle(SearchPanel.class).getString(
288: "ACSD_Tabs")); // NOI18N
289: }
290: okButton.getAccessibleContext().setAccessibleDescription(
291: NbBundle.getBundle(SearchPanel.class).getString(
292: "ACS_TEXT_BUTTON_SEARCH")); // NOI18N
293: cancelButton.getAccessibleContext().setAccessibleDescription(
294: NbBundle.getBundle(SearchPanel.class).getString(
295: "ACS_TEXT_BUTTON_CANCEL")); // NOI18N
296: }
297:
298: private JTabbedPane tabbedPane;
299:
300: /** @return name of criterion at index is modified. */
301: private String getTabText(int index) {
302: String text;
303: if (index == 0) {
304: text = NbBundle.getMessage(getClass(),
305: "BasicSearchForm.tabText"); //NOI18N
306: if (basicCriteriaPanel.getBasicSearchCriteria().isUsable()) {
307: text = text + " *"; //NOI18N
308: }
309: } else {
310: text = orderedSearchTypePanels.get(index - 1).getName();
311: }
312: return text;
313: }
314:
315: /**
316: * Gets array of customized search types.
317: *
318: * @return current state of customized search types.
319: */
320: List<SearchType> getCustomizedSearchTypes() {
321: if (orderedSearchTypePanels == null) {
322: return Collections.<SearchType> emptyList();
323: }
324:
325: List<SearchType> searchTypeList = new ArrayList<SearchType>(
326: orderedSearchTypePanels.size());
327: for (SearchTypePanel searchTypePanel : orderedSearchTypePanels) {
328: if (searchTypePanel.isCustomized()) {
329: searchTypeList.add(searchTypePanel.getSearchType());
330: }
331: }
332: return searchTypeList;
333: }
334:
335: /**
336: * Getter for return status property.
337: *
338: * @return the return status of this dialog - one of RET_OK or RET_CANCEL
339: */
340: public int getReturnStatus() {
341: return returnStatus;
342: }
343:
344: /** Closes dialog. */
345: private void doClose(int returnStatus) {
346:
347: if (orderedSearchTypePanels != null) {
348: for (SearchTypePanel panel : orderedSearchTypePanels) {
349: panel.removePropertyChangeListener(this );
350: }
351: }
352:
353: int selectedIndex = (tabbedPane == null) ? 0 : tabbedPane
354: .getSelectedIndex();
355: if (selectedIndex == 0) {
356: if (returnStatus == RET_OK) {
357: FindDialogMemory.getDefault().setLastUsedSearchType(
358: null);
359: basicCriteriaPanel.onOk();
360: }
361: } else if (selectedIndex > 0) {
362: SearchTypePanel panel = getSearchTypePanel(selectedIndex);
363: if (returnStatus == RET_OK) {
364: FindDialogMemory.getDefault().setLastUsedSearchType(
365: panel.getSearchType());
366: panel.onOk();
367: } else {
368: panel.onCancel();
369: }
370: }
371:
372: this .returnStatus = returnStatus;
373:
374: dialog.setVisible(false);
375: dialog.dispose();
376: }
377:
378: /**
379: * Shows dialog created from {@code DialogDescriptor} which wraps this instance.
380: */
381: void showDialog() {
382: String titleMsgKey = projectWide ? (searchAndReplace ? "LBL_ReplaceInProjects" //NOI18N
383: : "LBL_FindInProjects") //NOI18N
384: : (searchAndReplace ? "LBL_ReplaceInFiles" //NOI18N
385: : "LBL_FindInFiles"); //NOI18N
386:
387: DialogDescriptor dialogDescriptor = new DialogDescriptor(this ,
388: NbBundle.getMessage(getClass(), titleMsgKey), true,
389: new Object[] { okButton, cancelButton }, okButton,
390: DialogDescriptor.BOTTOM_ALIGN, new HelpCtx(getClass()),
391: this );
392: dialogDescriptor.setTitle(NbBundle.getMessage(getClass(),
393: titleMsgKey));
394:
395: dialog = DialogDisplayer.getDefault().createDialog(
396: dialogDescriptor);
397: dialog.setModal(true);
398: if (tabbedPane != null) {
399: tabbedPane.addFocusListener(this );
400: }
401:
402: dialog.pack();
403: dialog.setVisible(true);
404: }
405:
406: /**
407: * This method is called when the tabbed pane gets focus after the Find
408: * dialog is displayed.
409: * It lets the first tab to initialize focus.
410: */
411: public void focusGained(FocusEvent e) {
412: assert tabbedPane != null;
413:
414: tabbedPane.removeFocusListener(this );
415:
416: Component defaultComp = null;
417: int selectedIndex = tabbedPane.getSelectedIndex();
418: if (selectedIndex == 0) {
419: defaultComp = basicCriteriaPanel;
420: } else if (selectedIndex > 0) {
421: SearchTypePanel panel = getSearchTypePanel(selectedIndex);
422: if (panel != null) {
423: defaultComp = panel.customizerComponent; //may be null
424: }
425: }
426: if (defaultComp != null) {
427: defaultComp.requestFocusInWindow();
428: }
429:
430: tabbedPane.addChangeListener(this );
431: }
432:
433: /**
434: * This method is called when the tabbed pane looses focus.
435: * It does nothing and it is here just because this class declares that
436: * it implements the <code>FocusListener</code> interface.
437: *
438: * @see #focusGained(FocusEvent)
439: */
440: public void focusLost(FocusEvent e) {
441: //does nothing
442: }
443:
444: /**
445: * This method is called when tab selection changes and when values entered
446: * in the form for basic criteria become valid or invalid.
447: * Depending on the trigger, it either updates state of the <em>Find</em>
448: * button (enabled/disabled)
449: * or initializes the customizer below the selected tab.
450: *
451: * @see #updateIsCustomized()
452: * @see #tabSelectionChanged()
453: */
454: public void stateChanged(ChangeEvent e) {
455: if (e.getSource() == basicCriteriaPanel) {
456: updateIsCustomized();
457: if (tabbedPane != null) {
458: updateFirstTabText();
459: }
460: } else {
461: tabSelectionChanged();
462: }
463: }
464:
465: /**
466: * Called when a different tab is selected or when a tab becomes customized
467: * or uncustomized.
468: */
469: public void propertyChange(PropertyChangeEvent event) {
470: if (SearchTypePanel.PROP_CUSTOMIZED.equals(event
471: .getPropertyName())) {
472: updateIsCustomized();
473: if (tabbedPane != null) {
474: updateExtraTabsTexts();
475: }
476: }
477: }
478:
479: /**
480: * Updates label of the first tab.
481: *
482: * @see #updateExtraTabsTexts
483: */
484: private void updateFirstTabText() {
485: assert tabbedPane != null;
486: tabbedPane.setTitleAt(0, getTabText(0));
487: }
488:
489: /**
490: * Updates labels of all tabs except the first one.
491: *
492: * @see #updateFirstTabText
493: */
494: private void updateExtraTabsTexts() {
495: assert tabbedPane != null;
496: int tabCount = tabbedPane.getTabCount();
497: for (int i = 1; i < tabCount; i++) {
498: tabbedPane.setTitleAt(i, getTabText(i));
499: }
500: }
501:
502: /**
503: * Updates state of the <em>Find</em> dialog (enabled/disabled),
504: * depending on values entered in the form.
505: */
506: private void updateIsCustomized() {
507: okButton.setEnabled(checkIsCustomized());
508: }
509:
510: /**
511: * Checks whether valid criteria are entered in at least one criteria panel.
512: *
513: * @return {@code true} if some applicable criteria are entered,
514: * {@code false} otherwise
515: */
516: private boolean checkIsCustomized() {
517: if (basicCriteriaPanel.isUsable()) {
518: return true;
519: }
520:
521: if ((orderedSearchTypePanels != null)
522: && !orderedSearchTypePanels.isEmpty()) {
523: for (SearchTypePanel searchTypePanel : orderedSearchTypePanels) {
524: if (searchTypePanel.isCustomized()) {
525: return true;
526: }
527: }
528: }
529:
530: return false;
531: }
532:
533: private void tabSelectionChanged() {
534: assert tabbedPane != null;
535:
536: int selectedIndex = tabbedPane.getSelectedIndex();
537: if (selectedIndex == 0) {
538: //basicCriteriaPanel.setCriteria(/*PENDING*/);
539: } else if (selectedIndex > 0) {
540: SearchTypePanel panel = getSearchTypePanel(selectedIndex);
541: if (panel != null) {
542: panel.initializeWithObject();
543: }
544: }
545: }
546:
547: /**
548: * Gets a <code>SearchTypePanel</code> for the given tab index.
549: *
550: * @param index index of the tab to get the panel from
551: * @return <code>SearchTypePanel</code> at the given tab;
552: * or <code>null</code> if there is none at the given tab index
553: */
554: private SearchTypePanel getSearchTypePanel(int index) {
555: assert orderedSearchTypePanels != null;
556: assert index >= 1;
557:
558: return (--index < orderedSearchTypePanels.size()) ? orderedSearchTypePanels
559: .get(index)
560: : null;
561: }
562:
563: /**
564: * Gets the index for the the given {@code SearchType}
565: *
566: * @param searchTypeToFind {@code SearchType} to get the index for.
567: * @return index of the given {@code SearchType}, or {@code -1}
568: * if the given search type is {@code null} or if it is not present
569: * in the list of used search types
570: */
571: private int getIndexOfSearchType(SearchType searchTypeToFind) {
572:
573: if (searchTypeToFind == null) {
574: return -1;
575: }
576:
577: int index = -1;
578: for (SearchTypePanel searchTypePanel : orderedSearchTypePanels) {
579: index++;
580:
581: if (searchTypePanel.getSearchType().getClass() == searchTypeToFind
582: .getClass()) {
583: return index;
584: }
585: }
586:
587: return -1;
588: }
589:
590: }
|