2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.search;
44
import java.awt.BorderLayout;
45
import java.awt.CardLayout;
46
import java.awt.Dimension;
47
import java.awt.EventQueue;
48
import java.awt.Rectangle;
49
import javax.swing.BorderFactory;
50
import javax.swing.Box;
51
import javax.swing.JEditorPane;
52
import javax.swing.JLabel;
53
import javax.swing.JPanel;
54
import javax.swing.JScrollBar;
55
import javax.swing.JScrollPane;
56
import javax.swing.JTree;
57
import javax.swing.SwingConstants;
58
import javax.swing.UIManager;
59
import javax.swing.border.Border;
60
import javax.swing.event.TreeSelectionEvent;
61
import javax.swing.event.TreeSelectionListener;
62
import javax.swing.text.BadLocationException;
63
import javax.swing.text.Document;
64
import javax.swing.text.Element;
65
import javax.swing.text.StyledDocument;
66
import javax.swing.tree.TreePath;
67
import org.openide.ErrorManager;
68
import org.openide.text.NbDocument;
69
import org.openide.util.NbBundle;
70
import org.openide.util.RequestProcessor;
71
import static java.lang.Thread.NORM_PRIORITY;
74
* Panel for displaying context of a matching string within a file.
75
* When a node representing a matching string is selected in the tree
76
* of results, this panel displays a part of the file surrounding the selected
77
* matching string, with the matching string highlighted.
78
* When a node representing the whole file is selected, the beginning
79
* of the file is displayed.
81
* @author Tim Boudreau
82
* @author Marian Petras
84
final class ContextView extends JPanel implements TreeSelectionListener {
87
private static final String FILE_VIEW = "file view"; //NOI18N
89
private static final String MESSAGE_VIEW = "message view"; //NOI18N
92
private final CardLayout cardLayout;
93
/** editor pane actually displaying (part of) the file */
94
private final JEditorPane editorPane = new JEditorPane();
95
/** scroll pane containing the editor pane */
96
private final JScrollPane editorScroll;
97
/** displays location of the file above the editor pane */
98
private final JLabel lblPath = new JLabel();
99
/** displays message if no file is displayed */
100
private final JLabel lblMessage = new JLabel();
102
* displays content of file after it has been asynchronously loaded
103
* by the {@link #requestProcessor}
105
private final Displayer displayer = new Displayer();
106
/** used for asynchronous loading of files' contents */
107
private final RequestProcessor requestProcessor
108
= new RequestProcessor("TextView", NORM_PRIORITY, true); //NOI18N
111
private ResultModel resultModel;
113
private RequestProcessor.Task task = null;
115
private TextFetcher textFetcher = null;
117
private String displayedCard = null;
119
private String msgNoFileSelected = null;
121
private String msgMultipleFilesSelected = null;
122
/** the current MIME-type set for the {@link #editorPane} */
123
private String editorMimeType = null;
127
* @author Tim Boudreau
128
* @author Marian Petras
130
public ContextView(ResultModel resultModel) {
131
Border b = BorderFactory.createCompoundBorder(
132
BorderFactory.createMatteBorder( //outside border
134
UIManager.getColor("controlShadow")), //NOI18N
135
BorderFactory.createEmptyBorder( //inside border
137
lblPath.setBorder(b);
139
editorPane.setEditable(false);
140
editorPane.getCaret().setBlinkRate(0);
142
editorScroll = new JScrollPane(editorPane);
143
editorScroll.setViewportBorder(BorderFactory.createEmptyBorder());
144
editorScroll.setBorder(BorderFactory.createEmptyBorder());
146
JPanel fileViewPanel = new JPanel();
147
fileViewPanel.setLayout(new BorderLayout());
148
fileViewPanel.add(lblPath, BorderLayout.NORTH);
149
fileViewPanel.add(editorScroll, BorderLayout.CENTER);
151
Box messagePanel = Box.createVerticalBox();
152
messagePanel.add(Box.createVerticalGlue());
153
messagePanel.add(lblMessage);
154
messagePanel.add(Box.createVerticalGlue());
155
lblMessage.setAlignmentX(0.5f);
156
lblMessage.setHorizontalAlignment(SwingConstants.CENTER);
157
lblMessage.setEnabled(false);
159
setLayout(cardLayout = new CardLayout());
160
add(fileViewPanel, FILE_VIEW);
161
add(messagePanel, MESSAGE_VIEW);
163
setResultModel(resultModel);
167
public Dimension getMinimumSize() {
169
* Without this, the minimum width would be equal to the width
170
* of the {@linkplain #lblPath file path label}.
172
Dimension minSize = super.getMinimumSize();
179
void setResultModel(ResultModel resultModel) {
180
if (resultModel == this.resultModel) {
184
synchronized (this) { //PENDING - review synchronization
185
if (textFetcher != null) {
186
textFetcher.cancel();
190
this.resultModel = resultModel;
195
void bindToTreeSelection(final JTree tree) {
196
assert EventQueue.isDispatchThread();
198
displaySelectedFiles(tree);
199
tree.addTreeSelectionListener(this);
204
void unbindFromTreeSelection(final JTree tree) {
205
assert EventQueue.isDispatchThread();
207
tree.removeTreeSelectionListener(this);
209
synchronized (this) { //PENDING - review synchronization
210
if (textFetcher != null) {
211
textFetcher.cancel();
218
* Called when selection of nodes in the result tree changes.
220
public void valueChanged(TreeSelectionEvent e) {
221
displaySelectedFiles((JTree) e.getSource());
225
* Displays file(s) selected in the given tree.
227
* @author Marian Petras
229
private void displaySelectedFiles(final JTree tree) {
230
final TreePath[] selectedPaths = tree.getSelectionPaths();
231
if ((selectedPaths == null) || (selectedPaths.length == 0)) {
232
displayNoFileSelected();
233
} else if (selectedPaths.length > 1) {
234
displayMultipleItemsSelected();
236
assert selectedPaths.length == 1;
238
final TreePath path = selectedPaths[0];
239
int pathCount = path.getPathCount();
240
if (pathCount == 1) { //root node selected
241
displayNoFileSelected();
243
assert pathCount == 2 || pathCount == 3;
244
MatchingObject matchingObj;
246
if (pathCount == 2) { //file node selected
247
matchingObj = (MatchingObject) path.getLastPathComponent();
249
} else { //match node selected
250
TreePath matchingObjPath = path.getParentPath();
251
matchingObj = (MatchingObject)
252
matchingObjPath.getLastPathComponent();
253
int matchingObjRow = tree.getRowForPath(matchingObjPath);
254
int matchRow = tree.getRowForPath(path);
255
matchIndex = matchRow - matchingObjRow - 1;
257
displayFile(matchingObj, matchIndex);
264
private void displayNoFileSelected() {
265
if (msgNoFileSelected == null) {
266
msgNoFileSelected = NbBundle.getMessage(
268
"MsgNoFileSelected"); //NOI18N
270
displayMessage(msgNoFileSelected);
275
private void displayMultipleItemsSelected() {
276
if (msgMultipleFilesSelected == null) {
277
msgMultipleFilesSelected = NbBundle.getMessage(
279
"MsgMultipleFilesSelected");//NOI18N
281
displayMessage(msgMultipleFilesSelected);
286
private void displayMessage(String message) {
287
lblMessage.setText(message);
288
if (displayedCard != MESSAGE_VIEW) {
289
cardLayout.show(this, displayedCard = MESSAGE_VIEW);
294
* @author Tim Boudreau
295
* @author Marian Petras
297
private void displayFile(final MatchingObject matchingObj,
298
final int partIndex) {
299
assert EventQueue.isDispatchThread();
301
synchronized (displayer) { //PENDING - review synchronization
307
final Item item = new Item(resultModel, matchingObj, partIndex);
309
MatchingObject.InvalidityStatus invalidityStatus
310
= matchingObj.checkValidity();
311
if (invalidityStatus != null) {
312
displayMessage(invalidityStatus.getDescription(
313
matchingObj.getFile().getPath()));
317
requestText(item, displayer);
318
String description = matchingObj.getDescription();
319
lblPath.setText(description);
320
lblPath.setToolTipText(description); //in case it doesn't fit
325
* Fetch the text of an {@code Item}. Since the text is retrieved
326
* asynchronously, this method is passed a {@code TextDisplayer},
327
* which will get its {@code setText()} method called on the event thread
328
* after it has been loaded on a background thread.
330
* @param item item to be displayed by the text displayer
331
* @param textDisplayer displayer that should display the item
333
* @author Tim Boudreau
335
private void requestText(Item item, TextDisplayer textDisplayer) {
336
assert EventQueue.isDispatchThread();
338
synchronized (this) { //PENDING - review synchronization
339
if (textFetcher != null) {
340
if (textFetcher.replaceLocation(item, textDisplayer)) {
343
textFetcher.cancel();
347
if (textFetcher == null) {
348
textFetcher = new TextFetcher(item,
356
* Implementation of {@code TextDisplayer} which is passed to get the text
357
* of an item. The text is fetched from the file asynchronously, and then
358
* passed to {@link #setText()} to set the text, select the text the item
359
* represents and scroll it into view.
362
* @author Tim Boudreau
363
* @author Marian Petras
365
private class Displayer implements TextDisplayer, Runnable {
367
private TextDetail location;
370
* @author Tim Boudreau
372
public void setText(final String text,
374
final TextDetail location) {
375
assert EventQueue.isDispatchThread();
377
if ("content/unknown".equals(mimeType)) { //NOI18N
378
mimeType = "text/plain"; //Good idea? Bad? Hmm... //NOI18N
382
* Changing content type clears the text - so the content type
383
* (in this case, MIME-type only) must be set _before_ the text
386
if ((editorMimeType == null) || !editorMimeType.equals(mimeType)) {
387
editorPane.setContentType(mimeType);
388
editorMimeType = mimeType;
390
editorPane.setText(text);
392
if (displayedCard != FILE_VIEW) {
393
cardLayout.show(ContextView.this, displayedCard = FILE_VIEW);
396
if (location != null) {
397
//Let the L&F do anything it needs to do before we try to fiddle
398
//with it - get out of its way. Some Swing View classes don't
399
//have accurate position data until they've painted once.
400
this.location = location;
401
EventQueue.invokeLater(this);
409
* @author Tim Boudreau
410
* @author Marian Petras
413
assert EventQueue.isDispatchThread();
415
boolean scrolled = false;
417
if (!editorPane.isShowing()) {
421
if (location != null) {
422
final Document document = editorPane.getDocument();
423
if (document instanceof StyledDocument) {
424
StyledDocument styledDocument
425
= (StyledDocument) document;
426
int cursorOffset = getCursorOffset(
427
(StyledDocument) document,
428
location.getLine() - 1);
429
int startOff = cursorOffset + location.getColumn() - 1;
430
int endOff = startOff + location.getMarkLength();
431
editorPane.setSelectionStart(startOff);
432
editorPane.setSelectionEnd(endOff);
433
Rectangle r = editorPane.modelToView(startOff);
435
//Editor kit not yet updated, what to do
436
editorPane.scrollRectToVisible(r);
440
editorPane.getCaret().setBlinkRate(0);
441
editorPane.repaint();
443
} catch (BadLocationException e) {
444
//Maybe not even notify this - not all editors
445
//will have a 1:1 correspondence to file positions -
446
//it's perfectly reasonable for this to be thrown
447
ErrorManager.getDefault().notify( //PENDING - ErrorManager?
448
ErrorManager.INFORMATIONAL, e);
456
* Computes cursor offset of a given line of a document.
457
* The line number must be non-negative.
458
* If the line number is greater than number of the last line,
459
* the returned offset corresponds to the last line of the document.
461
* @param doc document to computer offset for
462
* @param line line number (first line = <code>0</code>)
463
* @return cursor offset of the beginning of the given line
465
* @author Marian Petras
467
private int getCursorOffset(StyledDocument doc, int line) {
468
assert EventQueue.isDispatchThread();
472
return NbDocument.findLineOffset(doc, line);
473
} catch (IndexOutOfBoundsException ex) {
474
/* probably line number out of bounds */
476
Element lineRootElement = NbDocument.findLineRootElement(doc);
477
int lineCount = lineRootElement.getElementCount();
478
if (line >= lineCount) {
479
return NbDocument.findLineOffset(doc, lineCount - 1);
488
private void scrollToTop() {
489
JScrollBar scrollBar;
491
scrollBar = editorScroll.getHorizontalScrollBar();
492
scrollBar.setValue(scrollBar.getMinimum());
494
scrollBar = editorScroll.getVerticalScrollBar();
495
scrollBar.setValue(scrollBar.getMinimum());