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.BorderLayout;
0045: import java.awt.CardLayout;
0046: import java.awt.event.ActionEvent;
0047: import java.awt.event.ActionListener;
0048: import java.awt.EventQueue;
0049: import java.awt.event.ItemEvent;
0050: import java.awt.event.ItemListener;
0051: import java.lang.ref.Reference;
0052: import java.text.MessageFormat;
0053: import java.util.List;
0054: import java.util.ResourceBundle;
0055: import java.lang.ref.WeakReference;
0056: import java.util.ArrayList;
0057: import java.util.Collection;
0058: import java.util.Collections;
0059: import javax.accessibility.AccessibleContext;
0060: import javax.swing.BorderFactory;
0061: import javax.swing.Box;
0062: import javax.swing.BoxLayout;
0063: import javax.swing.ImageIcon;
0064: import javax.swing.JButton;
0065: import javax.swing.JPanel;
0066: import javax.swing.JScrollPane;
0067: import javax.swing.JSplitPane;
0068: import javax.swing.JToggleButton;
0069: import javax.swing.JToolBar;
0070: import javax.swing.JTree;
0071: import javax.swing.SwingConstants;
0072: import javax.swing.tree.TreePath;
0073: import org.openide.awt.Mnemonics;
0074: import org.openide.nodes.Node;
0075: import org.openide.util.NbBundle;
0076: import org.openide.util.Utilities;
0077: import org.openide.windows.Mode;
0078: import org.openide.windows.TopComponent;
0079: import org.openide.windows.WindowManager;
0080: import org.openidex.search.SearchType;
0081: import static java.lang.Thread.NORM_PRIORITY;
0082:
0083: /**
0084: * Panel which displays search results in explorer like manner.
0085: * This panel is a singleton.
0086: *
0087: * @see <a href="doc-files/results-class-diagram.png">Class diagram</a>
0088: * @author Petr Kuzel, Jiri Mzourek, Peter Zavadsky
0089: * @author Marian Petras
0090: */
0091: final class ResultView extends TopComponent {
0092:
0093: /** display the matching string location in context by default? */
0094: private static final boolean SHOW_CONTEXT_BY_DEFAULT = true;
0095: /** */
0096: private static final String RESULTS_CARD = "results"; //NOI18N
0097: /** */
0098: private static final String ISSUES_CARD = "issues"; //NOI18N
0099:
0100: /** should the context view be visible when doing search & replace? */
0101: private boolean contextViewEnabled = SHOW_CONTEXT_BY_DEFAULT;
0102: /** is the context view visible? */
0103: private boolean contextViewVisible = false;
0104: /** */
0105: private double dividerLocation = -1.0d;
0106: /** */
0107: private boolean ignoreContextButtonToggle = false;
0108: /** */
0109: private boolean hasResults = false; //accessed only from the EventQueue
0110: /** */
0111: private int objectsCount = 0; //accessed only from the EventQueue
0112: /** */
0113: private volatile boolean hasDetails = false;
0114: /** */
0115: private volatile boolean searchInProgress = false;
0116:
0117: /** unique ID of <code>TopComponent</code> (singleton) */
0118: private static final String ID = "search-results"; //NOI18N
0119:
0120: /**
0121: * instance/singleton of this class
0122: *
0123: * @see #getInstance
0124: */
0125: private static Reference<ResultView> instance = null;
0126:
0127: /**
0128: * tree view for displaying found objects
0129: */
0130: private final JScrollPane treeView;
0131:
0132: /** Result data model. */
0133: private ResultModel resultModel = null;
0134: /** */
0135: private ResultTreeModel treeModel = null;
0136:
0137: /** */
0138: private final JTree tree;
0139: /** listens on various actions performed on nodes in the tree */
0140: private final NodeListener nodeListener;
0141:
0142: /** */
0143: private String searchScopeType;
0144: /** */
0145: private BasicSearchCriteria basicSearchCriteria;
0146: /** */
0147: private List<SearchType> searchTypes;
0148:
0149: /** template for displaying number of matching files found so far */
0150: private MessageFormat nodeCountFormat;
0151: /**
0152: * template for displaying of number of matching files and total number
0153: * of matches found so far
0154: */
0155: private MessageFormat nodeCountFormatFullText;
0156:
0157: /**
0158: * Returns a singleton of this class.
0159: *
0160: * @return singleton of this <code>TopComponent</code>
0161: */
0162: static synchronized ResultView getInstance() {
0163: ResultView view;
0164: view = (ResultView) WindowManager.getDefault()
0165: .findTopComponent(ID);
0166: if (view == null) {
0167: view = getDefault();
0168: }
0169: return view;
0170: }
0171:
0172: /**
0173: * Singleton accessor reserved for the window systemm only. The window
0174: * system calls this method to create an instance of this
0175: * <code>TopComponent</code> from a <code>.settings</code> file.
0176: * <p>
0177: * <em>This method should not be called anywhere except from the window
0178: * system's code. </em>
0179: *
0180: * @return singleton - instance of this class
0181: */
0182: public static synchronized ResultView getDefault() {
0183: ResultView view;
0184: if (instance == null) {
0185: view = new ResultView();
0186: instance = new WeakReference<ResultView>(view);
0187: } else {
0188: view = instance.get();
0189: if (view == null) {
0190: view = new ResultView();
0191: instance = new WeakReference<ResultView>(view);
0192: }
0193: }
0194: return view;
0195: }
0196:
0197: private final CardLayout contentCards;
0198: private final CardLayout resultViewCards;
0199: private final JPanel mainPanel;
0200: private final JPanel resultsPanel;
0201: private final JButton btnReplace;
0202: private final JButton btnModifySearch;
0203: private final JButton btnShowDetails;
0204: private final JButton btnStop;
0205: private final JButton btnPrev;
0206: private final JButton btnNext;
0207: private final JToggleButton btnDisplayContext;
0208:
0209: private JSplitPane splitPane;
0210: private ContextView contextView;
0211: private IssuesPanel issuesPanel;
0212:
0213: /** Creates a new <code>ResultView</code>. */
0214: private ResultView() {
0215: //PENDING - icons, accessible names, accessible descriptions
0216: JToolBar toolBar = new JToolBar(SwingConstants.VERTICAL);
0217: btnDisplayContext = new JToggleButton();
0218: btnDisplayContext.setIcon(new ImageIcon(Utilities.loadImage(
0219: "org/netbeans/modules/search/res/context.gif", true))); //NOI18N
0220: btnDisplayContext.setToolTipText(NbBundle.getMessage(
0221: getClass(), "TOOLTIP_ShowContext"));//NOI18N
0222: btnDisplayContext.getAccessibleContext()
0223: .setAccessibleDescription(
0224: NbBundle.getMessage(getClass(),
0225: "ACSD_ShowContext")); //NOI18N
0226: btnDisplayContext.setSelected(SHOW_CONTEXT_BY_DEFAULT);
0227: btnPrev = new JButton();
0228: btnPrev.setIcon(new ImageIcon(Utilities.loadImage(
0229: "org/netbeans/modules/search/res/prev.png", true))); //NOI18N
0230: btnNext = new JButton();
0231: btnNext.setIcon(new ImageIcon(Utilities.loadImage(
0232: "org/netbeans/modules/search/res/next.png", true))); //NOI18N
0233: toolBar.add(btnDisplayContext);
0234: toolBar.add(new JToolBar.Separator());
0235: toolBar.add(btnPrev);
0236: toolBar.add(btnNext);
0237: toolBar.setRollover(true);
0238: toolBar.setFloatable(false);
0239:
0240: treeModel = createTreeModel();
0241: tree = createTree(treeModel, nodeListener = new NodeListener());
0242: treeView = new JScrollPane(tree);
0243: treeView.getAccessibleContext().setAccessibleDescription(
0244: NbBundle.getMessage(ResultView.class, "ACS_TREEVIEW")); //NOI18N
0245: treeView.setBorder(BorderFactory.createEmptyBorder());
0246:
0247: resultsPanel = new JPanel(resultViewCards = new CardLayout());
0248:
0249: btnShowDetails = new JButton();
0250: btnModifySearch = new JButton();
0251: btnStop = new JButton();
0252: btnReplace = new JButton();
0253:
0254: /* initialize listening for buttons: */
0255: ButtonListener buttonListener = new ButtonListener();
0256: btnShowDetails.addActionListener(buttonListener);
0257: btnModifySearch.addActionListener(buttonListener);
0258: btnStop.addActionListener(buttonListener);
0259: btnReplace.addActionListener(buttonListener);
0260: btnPrev.addActionListener(buttonListener);
0261: btnNext.addActionListener(buttonListener);
0262: btnDisplayContext.addItemListener(buttonListener);
0263:
0264: Mnemonics.setLocalizedText(btnStop, NbBundle.getMessage(
0265: getClass(), "TEXT_BUTTON_STOP")); //NOI18N
0266: Mnemonics.setLocalizedText(btnShowDetails, NbBundle.getMessage(
0267: getClass(), "TEXT_BUTTON_FILL")); //NOI18N
0268: Mnemonics.setLocalizedText(btnReplace, NbBundle.getMessage(
0269: getClass(), "TEXT_BUTTON_REPLACE"));//NOI18N
0270: Mnemonics.setLocalizedText(btnModifySearch, NbBundle
0271: .getMessage(getClass(), "TEXT_BUTTON_CUSTOMIZE")); //NOI18N
0272:
0273: btnStop.setEnabled(false);
0274: btnShowDetails.setEnabled(false);
0275:
0276: btnReplace.setVisible(false);
0277:
0278: JPanel buttonsPanel = new JPanel();
0279: buttonsPanel.setLayout(new BoxLayout(buttonsPanel,
0280: BoxLayout.X_AXIS));
0281: buttonsPanel.add(btnReplace);
0282: buttonsPanel.add(Box.createHorizontalGlue());
0283: buttonsPanel.add(btnShowDetails);
0284: buttonsPanel.add(btnModifySearch);
0285: buttonsPanel.add(btnStop);
0286:
0287: mainPanel = new JPanel();
0288: mainPanel.setLayout(new BorderLayout(0, 5));
0289: mainPanel.add(toolBar, BorderLayout.WEST);
0290: mainPanel.add(resultsPanel, BorderLayout.CENTER);
0291: mainPanel.add(buttonsPanel, BorderLayout.SOUTH);
0292: //issue #46261 - "Search Results window must be opaque under GTK"
0293: mainPanel.setOpaque(true);
0294: mainPanel
0295: .setBorder(BorderFactory.createEmptyBorder(0, 0, 2, 0));
0296:
0297: setLayout(contentCards = new CardLayout());
0298: add(mainPanel, RESULTS_CARD);
0299:
0300: setName("Search Results"); //NOI18N
0301: setDisplayName(NbBundle.getMessage(ResultView.class,
0302: "TITLE_SEARCH_RESULTS")); //NOI18N
0303: setToolTipText(NbBundle.getMessage(ResultView.class,
0304: "TOOLTIP_SEARCH_RESULTS")); //NOI18N
0305: setIcon(Utilities
0306: .loadImage("org/netbeans/modules/search/res/find.gif")); //NOI18N
0307:
0308: initAccessibility();
0309:
0310: resultModelChanged();
0311: }
0312:
0313: /**
0314: */
0315: private static ResultTreeModel createTreeModel() {
0316: ResultTreeModel treeModel = new ResultTreeModel(null);
0317: treeModel.setRootDisplayName(getInitialRootNodeText());
0318: return treeModel;
0319: }
0320:
0321: /**
0322: */
0323: private static JTree createTree(ResultTreeModel treeModel,
0324: NodeListener nodeListener) {
0325: JTree tree = new JTree(treeModel);
0326: tree.setCellRenderer(new NodeRenderer(false));
0327: tree.putClientProperty("JTree.lineStyle", "Angled"); //NOI18N
0328:
0329: tree.addMouseListener(nodeListener);
0330: tree.addKeyListener(nodeListener);
0331: tree.addTreeWillExpandListener(nodeListener);
0332: tree.addTreeExpansionListener(nodeListener);
0333:
0334: tree.setToggleClickCount(0);
0335:
0336: return tree;
0337: }
0338:
0339: /**
0340: */
0341: private static String getInitialRootNodeText() {
0342: return NbBundle.getMessage(ResultView.class,
0343: "TEXT_Search_in_filesystems"); //NOI18N
0344: }
0345:
0346: /** Overriden to explicitely set persistence type of ResultView
0347: * to PERSISTENCE_NEVER */
0348: @Override
0349: public int getPersistenceType() {
0350: return TopComponent.PERSISTENCE_ALWAYS; // XXX protimluv
0351: }
0352:
0353: /** Replaces this in object stream. */
0354: @Override
0355: public Object writeReplace() {
0356: return new ResolvableHelper();
0357: }
0358:
0359: final public static class ResolvableHelper implements
0360: java.io.Serializable {
0361: static final long serialVersionUID = 7398708142639457544L;
0362:
0363: public Object readResolve() {
0364: return ResultView.getDefault();
0365: }
0366: }
0367:
0368: /**
0369: * Resolves to the {@linkplain #getDefault default instance} of this class.
0370: *
0371: * This method is necessary for correct functinality of window system's
0372: * mechanism of persistence of top components.
0373: */
0374: private Object readResolve() throws java.io.ObjectStreamException {
0375: return ResultView.getDefault();
0376: }
0377:
0378: private void initAccessibility() {
0379: ResourceBundle bundle = NbBundle.getBundle(ResultView.class);
0380: getAccessibleContext().setAccessibleName(
0381: bundle.getString("ACSN_ResultViewTopComponent")); //NOI18N
0382: getAccessibleContext().setAccessibleDescription(
0383: bundle.getString("ACSD_ResultViewTopComponent")); //NOI18N
0384:
0385: AccessibleContext accessCtx;
0386:
0387: accessCtx = treeView.getHorizontalScrollBar()
0388: .getAccessibleContext();
0389: accessCtx.setAccessibleName(bundle
0390: .getString("ACSN_HorizontalScrollbar")); //NOI18N
0391:
0392: accessCtx = treeView.getVerticalScrollBar()
0393: .getAccessibleContext();
0394: accessCtx.setAccessibleName(bundle
0395: .getString("ACSN_VerticalScrollbar")); //NOI18N
0396:
0397: accessCtx = treeView.getAccessibleContext();
0398: accessCtx
0399: .setAccessibleName(bundle.getString("ACSN_ResultTree")); //NOI18N
0400: accessCtx.setAccessibleDescription(bundle
0401: .getString("ACSD_ResultTree")); //NOI18N
0402:
0403: btnReplace.getAccessibleContext().setAccessibleDescription(
0404: bundle.getString("ACS_TEXT_BUTTON_REPLACE")); //NOI18N
0405: btnModifySearch.getAccessibleContext()
0406: .setAccessibleDescription(
0407: bundle.getString("ACS_TEXT_BUTTON_CUSTOMIZE")); //NOI18N
0408: btnShowDetails.getAccessibleContext().setAccessibleDescription(
0409: bundle.getString("ACS_TEXT_BUTTON_FILL")); //NOI18N
0410: btnStop.getAccessibleContext().setAccessibleDescription(
0411: bundle.getString("ACS_TEXT_BUTTON_STOP")); //NOI18N
0412: }
0413:
0414: /**
0415: * This method exists just to make the <code>close()</code> method
0416: * accessible via <code>Class.getDeclaredMethod(String, Class[])</code>.
0417: * It is used in <code>Manager</code>.
0418: */
0419: void closeResults() {
0420: close();
0421: }
0422:
0423: /** Send search details to output window. */
0424: public void fillOutput() {
0425: btnShowDetails.setEnabled(false);
0426: Manager.getInstance().schedulePrintingDetails(
0427: resultModel.getFoundObjects(), basicSearchCriteria,
0428: searchTypes);
0429: }
0430:
0431: /**
0432: */
0433: private void setRootDisplayName(String displayName) {
0434: treeModel.setRootDisplayName(displayName);
0435: }
0436:
0437: @Override
0438: protected void componentOpened() {
0439: assert EventQueue.isDispatchThread();
0440:
0441: Manager.getInstance().searchWindowOpened();
0442:
0443: setRootDisplayName(getInitialRootNodeText());
0444: /*selectAndActivateNode(root);*/
0445: if (searchScopeType == null) {
0446: btnModifySearch.setEnabled(false);
0447: }
0448: }
0449:
0450: @Override
0451: @SuppressWarnings("deprecation")
0452: public void requestFocus() {
0453: tree.requestFocus();
0454: }
0455:
0456: @Override
0457: @SuppressWarnings("deprecation")
0458: public boolean requestFocusInWindow() {
0459: return tree.requestFocusInWindow();
0460: }
0461:
0462: @Override
0463: protected void componentClosed() {
0464: assert EventQueue.isDispatchThread();
0465:
0466: rememberInput(null, null, null);
0467: Manager.getInstance().searchWindowClosed();
0468:
0469: if (contextView != null) {
0470: contextView.unbindFromTreeSelection(tree);
0471: contextView = null;
0472: }
0473: if (splitPane != null) {
0474: rememberDividerLocation();
0475: resultsPanel.remove(splitPane);
0476: splitPane = null;
0477: }
0478: if (issuesPanel != null) {
0479: removeIssuesPanel();
0480: }
0481: contextViewVisible = false;
0482: contextViewEnabled = SHOW_CONTEXT_BY_DEFAULT;
0483: }
0484:
0485: /**
0486: * Displays a message informing about the task which blocks the search
0487: * from being started. The search may also be blocked by a not yet finished
0488: * previous search task.
0489: *
0490: * @param blockingTask constant identifying the blocking task
0491: * @see Manager#SEARCHING
0492: * @see Manager#CLEANING_RESULT
0493: * @see Manager#PRINTING_DETAILS
0494: */
0495: void notifySearchPending(final int blockingTask) {
0496: assert EventQueue.isDispatchThread();
0497:
0498: removeIssuesPanel();
0499:
0500: String msgKey = null;
0501: switch (blockingTask) {
0502: case Manager.SEARCHING:
0503: msgKey = "TEXT_FINISHING_PREV_SEARCH"; //NOI18N
0504: break;
0505: case Manager.CLEANING_RESULT:
0506: msgKey = "TEXT_CLEANING_RESULT"; //NOI18N
0507: break;
0508: case Manager.PRINTING_DETAILS:
0509: msgKey = "TEXT_PRINTING_DETAILS"; //NOI18N
0510: break;
0511: default:
0512: assert false;
0513: }
0514: setRootDisplayName(NbBundle
0515: .getMessage(ResultView.class, msgKey));
0516: btnStop.setEnabled(true);
0517: btnReplace.setEnabled(false);
0518: }
0519:
0520: /**
0521: */
0522: void searchTaskStateChanged(final int changeType) {
0523: switch (changeType) {
0524: case Manager.EVENT_SEARCH_STARTED:
0525: searchStarted();
0526: break;
0527: case Manager.EVENT_SEARCH_FINISHED:
0528: searchFinished();
0529: break;
0530: case Manager.EVENT_SEARCH_INTERRUPTED:
0531: searchInterrupted();
0532: break;
0533: case Manager.EVENT_SEARCH_CANCELLED:
0534: searchCancelled();
0535: break;
0536: default:
0537: assert false;
0538: }
0539: }
0540:
0541: /**
0542: */
0543: void showAllDetailsFinished() {
0544: assert EventQueue.isDispatchThread();
0545:
0546: updateShowAllDetailsBtn();
0547: }
0548:
0549: /**
0550: */
0551: private void searchStarted() {
0552: assert EventQueue.isDispatchThread();
0553:
0554: removeIssuesPanel();
0555:
0556: setRootDisplayName(NbBundle.getMessage(ResultView.class,
0557: "TEXT_SEARCHING___")); //NOI18N
0558: nodeCountFormat = new MessageFormat(NbBundle.getMessage(
0559: getClass(), "TXT_RootSearchedNodes")); //NOI18N
0560: nodeCountFormatFullText = new MessageFormat(
0561: NbBundle.getMessage(getClass(),
0562: "TXT_RootSearchedNodesFulltext")); //NOI18N
0563:
0564: searchInProgress = true;
0565: updateShowAllDetailsBtn();
0566: btnModifySearch.setEnabled(true);
0567: btnStop.setEnabled(true);
0568: btnReplace.setEnabled(false);
0569: }
0570:
0571: /**
0572: */
0573: private void searchFinished() {
0574: assert EventQueue.isDispatchThread();
0575:
0576: setFinalRootNodeText();
0577:
0578: searchInProgress = false;
0579: hasDetails = (resultModel != null) ? resultModel.hasDetails()
0580: : false;
0581: updateShowAllDetailsBtn();
0582: btnStop.setEnabled(false);
0583: btnReplace.setEnabled(true);
0584: }
0585:
0586: /**
0587: */
0588: private void searchInterrupted() {
0589: assert EventQueue.isDispatchThread();
0590:
0591: searchFinished();
0592: }
0593:
0594: /**
0595: */
0596: private void searchCancelled() {
0597: assert EventQueue.isDispatchThread();
0598:
0599: setRootDisplayName(NbBundle.getMessage(ResultView.class,
0600: "TEXT_TASK_CANCELLED"));//NOI18N
0601:
0602: searchInProgress = true;
0603: updateShowAllDetailsBtn();
0604: btnStop.setEnabled(false);
0605: btnReplace.setEnabled(true);
0606: }
0607:
0608: /**
0609: */
0610: private void setFinalRootNodeText() {
0611: assert EventQueue.isDispatchThread();
0612:
0613: int resultSize = resultModel.size();
0614:
0615: if (resultModel.wasLimitReached()) {
0616: setRootDisplayName(NbBundle.getMessage(ResultView.class,
0617: "TEXT_MSG_FOUND_X_NODES_LIMIT", //NOI18N
0618: new Integer(resultSize)));
0619: return;
0620: }
0621:
0622: String baseMsg;
0623: if (resultSize == 0) {
0624: baseMsg = NbBundle.getMessage(ResultView.class,
0625: "TEXT_MSG_NO_NODE_FOUND"); //NOI18N
0626: } else {
0627: String bundleKey;
0628: Object[] args;
0629: if (resultModel.searchAndReplace) {
0630: bundleKey = "TEXT_MSG_FOUND_X_NODES_REPLACE"; //NOI18N
0631: args = new Object[4];
0632: } else if (resultModel.isBasicCriteriaOnly
0633: && resultModel.basicCriteria.isFullText()) {
0634: bundleKey = "TEXT_MSG_FOUND_X_NODES_FULLTEXT"; //NOI18N
0635: args = new Object[2];
0636: } else {
0637: bundleKey = "TEXT_MSG_FOUND_X_NODES"; //NOI18N
0638: args = new Object[1];
0639: }
0640: args[0] = new Integer(objectsCount);
0641: if (args.length > 1) {
0642: args[1] = new Integer(resultModel
0643: .getTotalDetailsCount());
0644: }
0645: if (args.length > 2) {
0646: args[2] = resultModel.basicCriteria
0647: .getTextPatternExpr();
0648: args[3] = resultModel.basicCriteria.getReplaceExpr();
0649: }
0650: baseMsg = NbBundle.getMessage(getClass(), bundleKey, args);
0651: }
0652: String exMsg = resultModel.getExceptionMsg();
0653: String msg = exMsg == null ? baseMsg : baseMsg + " (" + exMsg
0654: + ")"; //NOI18N
0655: setRootDisplayName(msg);
0656: }
0657:
0658: /**
0659: */
0660: private void updateShowAllDetailsBtn() {
0661: assert EventQueue.isDispatchThread();
0662:
0663: btnShowDetails.setEnabled(hasResults && !searchInProgress
0664: && hasDetails);
0665: }
0666:
0667: /** Set new model. */
0668: synchronized void setResultModel(final ResultModel resultModel) {
0669: assert EventQueue.isDispatchThread();
0670:
0671: if ((this .resultModel == null) && (resultModel == null)) {
0672: return;
0673: }
0674:
0675: boolean hadCheckBoxes = (this .resultModel != null)
0676: && this .resultModel.searchAndReplace;
0677: boolean hasCheckBoxes = (resultModel != null)
0678: && resultModel.searchAndReplace;
0679:
0680: this .resultModel = resultModel; //may be null!
0681:
0682: tree.setModel(treeModel = new ResultTreeModel(resultModel));
0683: if (hasCheckBoxes != hadCheckBoxes) {
0684: tree.setCellRenderer(new NodeRenderer(hasCheckBoxes));
0685: btnReplace.setVisible(hasCheckBoxes);
0686: }
0687: if (resultModel != null) {
0688: hasResults = !resultModel.isEmpty();
0689: hasDetails = hasResults && resultModel.hasDetails();
0690: resultModel.setObserver(this );
0691: } else {
0692: hasResults = false;
0693: hasDetails = false;
0694: }
0695:
0696: resultModelChanged();
0697:
0698: updateShowAllDetailsBtn();
0699: }
0700:
0701: /**
0702: */
0703: private void resultModelChanged() {
0704: updateDisplayContextButton();
0705: updateContextViewVisibility();
0706: if (contextView != null) {
0707: contextView.setResultModel(resultModel);
0708: }
0709: nodeListener.setSelectionChangeEnabled(true);
0710:
0711: btnPrev.setEnabled(resultModel != null);
0712: btnNext.setEnabled(resultModel != null);
0713: resetMatchingObjIndexCache();
0714:
0715: objectsCount = 0;
0716: }
0717:
0718: /**
0719: * Checks whether this result view displays search results for operation
0720: * <em>search & replace</em> or for plain search.
0721: *
0722: * @return {@code true} if results for <em>search & replace</em>
0723: * are displayed, {@code false} otherwise
0724: */
0725: private boolean isSearchAndReplace() {
0726: return (resultModel != null) && resultModel.searchAndReplace;
0727: }
0728:
0729: /**
0730: * Enables or disables the <em>Display Context</em> button,
0731: * according to the result model currently displayed.
0732: *
0733: * @see #updateContextViewVisibility
0734: */
0735: private void updateDisplayContextButton() {
0736: boolean searchAndReplace = isSearchAndReplace();
0737: btnDisplayContext.setEnabled(searchAndReplace);
0738:
0739: ignoreContextButtonToggle = true;
0740: try {
0741: btnDisplayContext.setSelected(searchAndReplace
0742: && contextViewEnabled);
0743: } finally {
0744: ignoreContextButtonToggle = false;
0745: }
0746: }
0747:
0748: /**
0749: * Shows or hides the context view, according to the state of the
0750: * <em>Display Context</em> button.
0751: *
0752: * @see #updateDisplayContextButton
0753: */
0754: private void updateContextViewVisibility() {
0755: setContextViewVisible(isSearchAndReplace()
0756: && contextViewEnabled);
0757: }
0758:
0759: /**
0760: * Shows or hides the context view.
0761: */
0762: private void setContextViewVisible(boolean visible) {
0763: assert EventQueue.isDispatchThread();
0764: assert (splitPane == null) == (contextView == null);
0765:
0766: final int componentCount = resultsPanel.getComponentCount();
0767: if ((visible == contextViewVisible) && (componentCount != 0)) {
0768: return;
0769: }
0770:
0771: this .contextViewVisible = visible;
0772:
0773: final String cardName;
0774: if (visible == false) {
0775: cardName = "tree only"; //NOI18N
0776: /*
0777: * This code is executed either the first time the result view
0778: * is displayed or when the context view is closed.
0779: * In the former case, we must add the tree view simply because
0780: * the result view does not contain it yet.
0781: * In the latter case, we must add it, too, because it was
0782: * removed from the resultsPanel the last time the context view
0783: * was displayed.
0784: */
0785: assert componentCount < 2;
0786: /*
0787: * The following line removes the treeView from the splitPane
0788: * as a side-effect.
0789: */
0790: resultsPanel.add(treeView, cardName);
0791: if (contextView != null) {
0792: contextView.unbindFromTreeSelection(tree);
0793: rememberDividerLocation();
0794: }
0795: } else {
0796: assert resultModel != null;
0797:
0798: cardName = "tree and context"; //NOI18N
0799: if (splitPane == null) {
0800: contextView = new ContextView(resultModel);
0801: splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
0802: true, //continuous layout
0803: treeView, //left pane
0804: contextView); //right pane
0805: splitPane.setBorder(BorderFactory.createEmptyBorder());
0806: splitPane.setResizeWeight(0.4d);
0807: resultsPanel.add(splitPane, cardName);
0808: } else {
0809: /*
0810: * The following line removes the treeView from the resultsPanel
0811: * as a side-effect.
0812: */
0813: splitPane.setLeftComponent(treeView);
0814: }
0815: setDividerLocation();
0816: contextView.bindToTreeSelection(tree);
0817: }
0818: /*
0819: * Changing cards hides the component that represented the previous
0820: * card and shows the new card. In this case, if card "tree only"
0821: * is going to be replaced with "tree and context", component 'treeView'
0822: * is going to be hidden. But we want 'treeView' to be visible - just
0823: * at a different context (inside the split pane). To ensure visibility
0824: * of 'treeView', we must make call setVisible(true) explicitely
0825: * after the card swap.
0826: */
0827: resultViewCards.show(resultsPanel, cardName);
0828: treeView.setVisible(true);
0829: }
0830:
0831: /**
0832: */
0833: private void rememberDividerLocation() {
0834: if (splitPane == null) {
0835: return;
0836: }
0837:
0838: dividerLocation = (double) splitPane.getDividerLocation()
0839: / (double) (splitPane.getWidth() - splitPane
0840: .getDividerSize());
0841: }
0842:
0843: /**
0844: */
0845: private void setDividerLocation() {
0846: assert splitPane != null;
0847:
0848: if (dividerLocation != -1.0d) {
0849: splitPane.setDividerLocation(dividerLocation);
0850: }
0851: }
0852:
0853: /**
0854: */
0855: void objectFound(Object foundObject, final int totalDetailsCount) {
0856: assert !EventQueue.isDispatchThread();
0857:
0858: EventQueue.invokeLater(new Runnable() {
0859: public void run() {
0860: updateObjectsCount(totalDetailsCount);
0861: }
0862: });
0863: }
0864:
0865: /**
0866: * Updates the number of found nodes in the name of the root node.
0867: */
0868: private synchronized void updateObjectsCount(
0869: final int totalDetailsCount) {
0870: assert EventQueue.isDispatchThread();
0871:
0872: if (resultModel == null) {
0873: // a new search was scheduled, so don't do anything
0874: return;
0875: }
0876:
0877: objectsCount++;
0878: hasResults = true;
0879:
0880: setRootDisplayName(resultModel.isBasicCriteriaOnly
0881: && resultModel.basicCriteria.isFullText() ? nodeCountFormatFullText
0882: .format(new Object[] { new Integer(objectsCount),
0883: new Integer(totalDetailsCount) })
0884: : nodeCountFormat.format(new Object[] { new Integer(
0885: objectsCount) }));
0886: }
0887:
0888: /**
0889: */
0890: void removeIssuesPanel() {
0891: if (issuesPanel != null) {
0892: remove(issuesPanel);
0893: issuesPanel = null;
0894: }
0895: contentCards.show(this , RESULTS_CARD);
0896: }
0897:
0898: /**
0899: * Jumps to the next or previous match.
0900: *
0901: * @param forward {@code true} for the <em>next</em> match,
0902: * {@code false} for the <em>previous</em> match
0903: * @see #goToPrev()
0904: */
0905: private void goToNext(final boolean forward) {
0906: assert EventQueue.isDispatchThread();
0907: assert resultModel != null;
0908:
0909: if (!hasResults) {
0910: return;
0911: }
0912:
0913: TreePath leadPath = tree.getLeadSelectionPath();
0914: if (leadPath == null) {
0915: leadPath = new TreePath(tree.getModel().getRoot());
0916: }
0917:
0918: final TreePath nextPath = findNextPath(leadPath, forward);
0919:
0920: if (nextPath != null) {
0921: /*
0922: * If we did not expand the parent path explicitely,
0923: * attached TreeSelectionListeners might not be able to get
0924: * row for the 'nextPath' because of the parent path still
0925: * collapsed.
0926: */
0927: tree.expandPath(nextPath.getParentPath());
0928:
0929: tree.setSelectionPath(nextPath);
0930: tree.scrollRectToVisible(tree.getPathBounds(nextPath));
0931: }
0932: }
0933:
0934: /**
0935: */
0936: private TreePath findNextPath(final TreePath forPath,
0937: final boolean forward) {
0938: TreePath nextPath;
0939:
0940: Object root;
0941: TreePath parentPath = forPath.getParentPath();
0942: if (parentPath == null) { //selected = root node
0943: root = forPath.getLastPathComponent();
0944: nextPath = forward ? getNextDetail(root, null, -1, forward)
0945: : null;
0946: } else {
0947: MatchingObject matchingObj;
0948: Object lastComp = forPath.getLastPathComponent();
0949: if (lastComp.getClass() == MatchingObject.class) {
0950: root = parentPath.getLastPathComponent();
0951: matchingObj = (MatchingObject) lastComp;
0952: nextPath = getNextDetail(root, matchingObj, -1, forward);
0953: } else {
0954: root = parentPath.getParentPath()
0955: .getLastPathComponent();
0956: Object parentComp = parentPath.getLastPathComponent();
0957: assert parentComp.getClass() == MatchingObject.class;
0958: matchingObj = (MatchingObject) parentComp;
0959:
0960: int parentPathRow = tree.getRowForPath(parentPath);
0961: int row = tree.getRowForPath(forPath);
0962: int index = row - parentPathRow - 1;
0963: nextPath = getNextDetail(root, matchingObj, index,
0964: forward);
0965: }
0966: }
0967:
0968: return nextPath;
0969: }
0970:
0971: /**
0972: * Finds path to the first detail node following or preceding
0973: * the node specified by couple ({@code MatchingObject}, detail index).
0974: *
0975: * @param root root object of the tree model
0976: * (it will be used as a first component of any
0977: * non-{@code null} returned path)
0978: * @param matchingObj the currently selected {@code MatchingObject},
0979: * or {@code null} if the tree's root is selected
0980: * @param detailIndex index of the currently selected detail node,
0981: * or {@code -1} if no detail node is selected
0982: * @param forward {@code true} for forward search,
0983: * {@code false} for backward search
0984: * @return path to the next detail node,
0985: * or {@code null} if no next detail node is available
0986: */
0987: private TreePath getNextDetail(final Object root,
0988: final MatchingObject matchingObj, final int detailIndex,
0989: final boolean forward) {
0990: if (matchingObj != null) {
0991: int nextDetailIndex = forward ? detailIndex + 1
0992: : detailIndex - 1;
0993: if ((nextDetailIndex >= 0)
0994: && (nextDetailIndex < resultModel
0995: .getDetailsCount(matchingObj))) {
0996: return new TreePath(
0997: new Object[] {
0998: root,
0999: matchingObj,
1000: resultModel.getDetails(matchingObj)[nextDetailIndex] });
1001: }
1002: } else /*(matchingObj == null)*/if (!forward) {
1003: return null;
1004: }
1005:
1006: final MatchingObject[] matchingObjs = resultModel
1007: .getMatchingObjects();
1008: int currMatchingObjIndex = getMatchingObjIndex(matchingObjs,
1009: matchingObj, forward);
1010: MatchingObject nextMatchingObj;
1011: int i;
1012:
1013: if (forward) {
1014: for (i = currMatchingObjIndex + 1; i < matchingObjs.length; i++) {
1015: nextMatchingObj = matchingObjs[i];
1016: if (resultModel.hasDetails(nextMatchingObj)) {
1017: return new TreePath(
1018: new Object[] {
1019: root,
1020: nextMatchingObj,
1021: resultModel
1022: .getDetails(nextMatchingObj)[0] });
1023: }
1024: }
1025: } else {
1026: for (i = currMatchingObjIndex - 1; i >= 0; i--) {
1027: nextMatchingObj = matchingObjs[i];
1028: if (resultModel.hasDetails(nextMatchingObj)) {
1029: Node[] details = resultModel
1030: .getDetails(nextMatchingObj);
1031: return new TreePath(new Object[] { root,
1032: nextMatchingObj,
1033: details[details.length - 1] });
1034: }
1035: }
1036: }
1037: return null;
1038: }
1039:
1040: private MatchingObject matchingObjIndexCacheObj = null;
1041: private int matchingObjIndexCacheIndex = -1;
1042:
1043: /**
1044: */
1045: private void resetMatchingObjIndexCache() {
1046: matchingObjIndexCacheObj = null;
1047: matchingObjIndexCacheIndex = -1;
1048: }
1049:
1050: /**
1051: */
1052: private int getMatchingObjIndex(
1053: final MatchingObject[] matchingObjs,
1054: final MatchingObject matchingObj, final boolean forward) {
1055: if (matchingObj == null) {
1056: return -1;
1057: }
1058:
1059: if (matchingObj == matchingObjIndexCacheObj) {
1060: assert (matchingObjIndexCacheIndex != -1);
1061: return matchingObjIndexCacheIndex;
1062: }
1063:
1064: int foundIndex = -1;
1065:
1066: /* Probe several positions below and above the cached index: */
1067: if (matchingObjIndexCacheIndex != -1) {
1068: final int quickSearchRange = 3;
1069: int startIndex;
1070: int endIndex;
1071: int i;
1072: if (forward) {
1073: startIndex = Math.min(matchingObjIndexCacheIndex + 1,
1074: matchingObjs.length - 1);
1075: endIndex = Math.min(matchingObjIndexCacheIndex
1076: + quickSearchRange, matchingObjs.length - 1);
1077: for (i = startIndex; i <= endIndex; i++) {
1078: if (matchingObjs[i] == matchingObj) {
1079: foundIndex = i;
1080: break;
1081: }
1082: }
1083: if ((foundIndex == -1)
1084: && (matchingObjIndexCacheIndex > 0)) {
1085: if (matchingObjs[i = matchingObjIndexCacheIndex - 1] == matchingObj) {
1086: foundIndex = i;
1087: }
1088: }
1089: } else { /*backward*/
1090: startIndex = Math
1091: .max(matchingObjIndexCacheIndex - 1, 0);
1092: endIndex = Math.max(matchingObjIndexCacheIndex
1093: - quickSearchRange, 0);
1094: for (i = startIndex; i >= endIndex; i--) {
1095: if (matchingObjs[i] == matchingObj) {
1096: foundIndex = i;
1097: break;
1098: }
1099: }
1100: if ((foundIndex == -1)
1101: && (matchingObjIndexCacheIndex < matchingObjs.length - 1)) {
1102: if (matchingObjs[i = matchingObjIndexCacheIndex + 1] == matchingObj) {
1103: foundIndex = i;
1104: }
1105: }
1106: }
1107: }
1108:
1109: /* Nothing found near the cached position - search from the beginning */
1110: if (foundIndex == -1) {
1111: for (int i = 0; i < matchingObjs.length; i++) {
1112: if (matchingObj == matchingObjs[i]) {
1113: foundIndex = i;
1114: break;
1115: }
1116: }
1117: }
1118:
1119: /* If the matching index is found, store it to the cache: */
1120: if (foundIndex != -1) {
1121: matchingObjIndexCacheObj = matchingObj;
1122: matchingObjIndexCacheIndex = foundIndex;
1123: }
1124:
1125: return foundIndex;
1126: }
1127:
1128: /**
1129: */
1130: void rememberInput(String searchScopeType,
1131: BasicSearchCriteria basicSearchCriteria,
1132: List<SearchType> searchTypes) {
1133: this .searchScopeType = searchScopeType;
1134: this .basicSearchCriteria = basicSearchCriteria;
1135: this .searchTypes = searchTypes;
1136: }
1137:
1138: /** (Re)open the dialog window for entering (new) search criteria. */
1139: private void customizeCriteria() {
1140: assert EventQueue.isDispatchThread();
1141:
1142: BasicSearchCriteria basicSearchCriteriaClone = (basicSearchCriteria != null) ? new BasicSearchCriteria(
1143: basicSearchCriteria)
1144: : new BasicSearchCriteria();
1145: List<SearchType> extraSearchTypesClones = cloneAvailableSearchTypes(searchTypes);
1146:
1147: SearchPanel searchPanel = new SearchPanel(SearchScopeRegistry
1148: .getDefault().getSearchScopes(), searchScopeType,
1149: basicSearchCriteriaClone, extraSearchTypesClones);
1150: searchPanel.showDialog();
1151:
1152: if (searchPanel.getReturnStatus() != SearchPanel.RET_OK) {
1153: return;
1154: }
1155:
1156: SearchScope searchScope = searchPanel.getSearchScope();
1157: searchScopeType = searchScope.getTypeId();
1158: basicSearchCriteria = searchPanel.getBasicSearchCriteria();
1159: searchTypes = searchPanel.getSearchTypes();
1160:
1161: Manager.getInstance().scheduleSearchTask(
1162: new SearchTask(searchScope, basicSearchCriteria,
1163: searchPanel.getCustomizedSearchTypes()));
1164: }
1165:
1166: /**
1167: * Makes a list of clones of given {@code SearchType}s.
1168: * The given {@code SearchType}s are checked such that those that are
1169: * no longer supported by the current set of IDE modules are skipped.
1170: *
1171: * @param searchTypes list of {@code SearchType}s to be cloned
1172: * @return list of cloned {@code SearchType}s, with unsupported
1173: * {@code SearchType}s omitted
1174: */
1175: private static List<SearchType> cloneAvailableSearchTypes(
1176: List<SearchType> searchTypes) {
1177:
1178: /* build a collection of class names of supported SearchTypes: */
1179: Collection<? extends SearchType> availableSearchTypes = Utils
1180: .getSearchTypes();
1181: Collection<String> availableSearchTypeNames = new ArrayList<String>(
1182: availableSearchTypes.size());
1183: for (SearchType searchType : availableSearchTypes) {
1184: availableSearchTypeNames.add(searchType.getClass()
1185: .getName());
1186: }
1187:
1188: if (availableSearchTypeNames.isEmpty()) {
1189: return Collections.<SearchType> emptyList(); //trivial case
1190: }
1191:
1192: /* clone all supported SearchTypes: */
1193: List<SearchType> clones = new ArrayList<SearchType>(searchTypes
1194: .size());
1195: for (SearchType searchType : searchTypes) {
1196: if (availableSearchTypeNames.contains(searchType.getClass()
1197: .getName())) {
1198: clones.add((SearchType) searchType.clone());
1199: }
1200: }
1201: return clones;
1202: }
1203:
1204: /**
1205: * Called when the <em>Replace</em> button is pressed.
1206: */
1207: private void replaceMatches() {
1208: assert EventQueue.isDispatchThread();
1209:
1210: nodeListener.setSelectionChangeEnabled(false);
1211: btnReplace.setEnabled(false);
1212:
1213: Manager.getInstance().scheduleReplaceTask(
1214: new ReplaceTask(resultModel.getMatchingObjects()));
1215: }
1216:
1217: /**
1218: */
1219: void closeAndSendFocusToEditor() {
1220: assert EventQueue.isDispatchThread();
1221:
1222: close();
1223:
1224: Mode m = WindowManager.getDefault().findMode("editor"); //NOI18N
1225: if (m != null) {
1226: TopComponent tc = m.getSelectedTopComponent();
1227: if (tc != null) {
1228: tc.requestActive();
1229: }
1230: }
1231: }
1232:
1233: /**
1234: */
1235: void displayIssuesToUser(String title, String[] problems,
1236: boolean reqAtt) {
1237: assert EventQueue.isDispatchThread();
1238: assert issuesPanel == null;
1239:
1240: issuesPanel = new IssuesPanel(title, problems);
1241: add(issuesPanel, ISSUES_CARD);
1242: contentCards.show(this , ISSUES_CARD);
1243:
1244: if (!isOpened()) {
1245: open();
1246: }
1247: if (reqAtt) {
1248: requestAttention(true);
1249: }
1250: }
1251:
1252: /**
1253: */
1254: void rescan() {
1255: assert EventQueue.isDispatchThread();
1256:
1257: removeIssuesPanel();
1258: Manager.getInstance().scheduleSearchTaskRerun();
1259: }
1260:
1261: @Override
1262: protected String preferredID() {
1263: return getClass().getName();
1264: }
1265:
1266: /**
1267: */
1268: private class ButtonListener implements ActionListener,
1269: ItemListener {
1270:
1271: public void actionPerformed(ActionEvent e) {
1272: Object source = e.getSource();
1273: if (source == btnStop) {
1274: Manager.getInstance().stopSearching();
1275: } else if (source == btnModifySearch) {
1276: customizeCriteria();
1277: } else if (source == btnShowDetails) {
1278: fillOutput();
1279: } else if (source == btnReplace) {
1280: replaceMatches();
1281: } else if (source == btnPrev) {
1282: goToNext(false);
1283: } else if (source == btnNext) {
1284: goToNext(true);
1285: } else {
1286: assert false;
1287: }
1288: }
1289:
1290: /**
1291: * Called when the Display Context button is selected or deselected.
1292: */
1293: public void itemStateChanged(ItemEvent e) {
1294: assert e.getSource() == btnDisplayContext;
1295: if (!ignoreContextButtonToggle) {
1296: contextViewEnabled = (e.getStateChange() == ItemEvent.SELECTED);
1297: updateContextViewVisibility();
1298: }
1299: }
1300:
1301: }
1302:
1303: }
|