~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to utilities/src/org/netbeans/modules/search/ContextView.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
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]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
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.
 
29
 *
 
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.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.search;
 
43
 
 
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;
 
72
 
 
73
/**
 
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.
 
80
 *
 
81
 * @author  Tim Boudreau
 
82
 * @author  Marian Petras
 
83
 */
 
84
final class ContextView extends JPanel implements TreeSelectionListener {
 
85
    
 
86
    /** */
 
87
    private static final String FILE_VIEW = "file view";                //NOI18N
 
88
    /** */
 
89
    private static final String MESSAGE_VIEW = "message view";          //NOI18N
 
90
    
 
91
    /** */
 
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();
 
101
    /**
 
102
     * displays content of file after it has been asynchronously loaded
 
103
     * by the {@link #requestProcessor}
 
104
     */
 
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
 
109
    
 
110
    /** */
 
111
    private ResultModel resultModel;
 
112
    /** */
 
113
    private RequestProcessor.Task task = null;
 
114
    /** */
 
115
    private TextFetcher textFetcher = null;
 
116
    /** */
 
117
    private String displayedCard = null;
 
118
    /** */
 
119
    private String msgNoFileSelected = null;
 
120
    /** */
 
121
    private String msgMultipleFilesSelected = null;
 
122
    /** the current MIME-type set for the {@link #editorPane} */
 
123
    private String editorMimeType = null;
 
124
    
 
125
    /**
 
126
     * 
 
127
     * @author  Tim Boudreau
 
128
     * @author  Marian Petras
 
129
     */
 
130
    public ContextView(ResultModel resultModel) {
 
131
        Border b = BorderFactory.createCompoundBorder(
 
132
                BorderFactory.createMatteBorder(        //outside border
 
133
                                0, 0, 1, 0, 
 
134
                                UIManager.getColor("controlShadow")),   //NOI18N
 
135
                BorderFactory.createEmptyBorder(        //inside border
 
136
                                5, 5, 1, 5));
 
137
        lblPath.setBorder(b);
 
138
        
 
139
        editorPane.setEditable(false);
 
140
        editorPane.getCaret().setBlinkRate(0);
 
141
        
 
142
        editorScroll = new JScrollPane(editorPane);
 
143
        editorScroll.setViewportBorder(BorderFactory.createEmptyBorder());
 
144
        editorScroll.setBorder(BorderFactory.createEmptyBorder());
 
145
        
 
146
        JPanel fileViewPanel = new JPanel();
 
147
        fileViewPanel.setLayout(new BorderLayout());
 
148
        fileViewPanel.add(lblPath, BorderLayout.NORTH);
 
149
        fileViewPanel.add(editorScroll, BorderLayout.CENTER);
 
150
        
 
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);
 
158
        
 
159
        setLayout(cardLayout = new CardLayout());
 
160
        add(fileViewPanel, FILE_VIEW);
 
161
        add(messagePanel, MESSAGE_VIEW);
 
162
        
 
163
        setResultModel(resultModel);
 
164
    }
 
165
    
 
166
    @Override
 
167
    public Dimension getMinimumSize() {
 
168
        /*
 
169
         * Without this, the minimum width would be equal to the width
 
170
         * of the {@linkplain #lblPath file path label}.
 
171
         */
 
172
        Dimension minSize = super.getMinimumSize();
 
173
        minSize.width = 0;
 
174
        return minSize;
 
175
    }
 
176
    
 
177
    /**
 
178
     */
 
179
    void setResultModel(ResultModel resultModel) {
 
180
        if (resultModel == this.resultModel) {
 
181
            return;
 
182
        }
 
183
        
 
184
        synchronized (this) {           //PENDING - review synchronization
 
185
            if (textFetcher != null) {
 
186
                textFetcher.cancel();
 
187
                textFetcher = null;
 
188
            }
 
189
        }
 
190
        this.resultModel = resultModel;
 
191
    }
 
192
    
 
193
    /**
 
194
     */
 
195
    void bindToTreeSelection(final JTree tree) {
 
196
        assert EventQueue.isDispatchThread();
 
197
        
 
198
        displaySelectedFiles(tree);
 
199
        tree.addTreeSelectionListener(this);
 
200
    }
 
201
    
 
202
    /**
 
203
     */
 
204
    void unbindFromTreeSelection(final JTree tree) {
 
205
        assert EventQueue.isDispatchThread();
 
206
        
 
207
        tree.removeTreeSelectionListener(this);
 
208
        
 
209
        synchronized (this) {           //PENDING - review synchronization
 
210
            if (textFetcher != null) {
 
211
                textFetcher.cancel();
 
212
                textFetcher = null;
 
213
            }
 
214
        }
 
215
    }
 
216
 
 
217
    /**
 
218
     * Called when selection of nodes in the result tree changes.
 
219
     */
 
220
    public void valueChanged(TreeSelectionEvent e) {
 
221
        displaySelectedFiles((JTree) e.getSource());
 
222
    }
 
223
    
 
224
    /**
 
225
     * Displays file(s) selected in the given tree.
 
226
     * 
 
227
     * @author  Marian Petras
 
228
     */
 
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();
 
235
        } else {
 
236
            assert selectedPaths.length == 1;
 
237
            
 
238
            final TreePath path = selectedPaths[0];
 
239
            int pathCount = path.getPathCount();
 
240
            if (pathCount == 1) {                   //root node selected
 
241
                displayNoFileSelected();
 
242
            } else {
 
243
                assert pathCount == 2 || pathCount == 3;
 
244
                MatchingObject matchingObj;
 
245
                int matchIndex;
 
246
                if (pathCount == 2) {               //file node selected
 
247
                    matchingObj = (MatchingObject) path.getLastPathComponent();
 
248
                    matchIndex = -1;
 
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;
 
256
                }
 
257
                displayFile(matchingObj, matchIndex);
 
258
            }
 
259
        }
 
260
    }
 
261
    
 
262
    /**
 
263
     */
 
264
    private void displayNoFileSelected() {
 
265
        if (msgNoFileSelected == null) {
 
266
            msgNoFileSelected = NbBundle.getMessage(
 
267
                                            getClass(),
 
268
                                            "MsgNoFileSelected");       //NOI18N
 
269
        }
 
270
        displayMessage(msgNoFileSelected);
 
271
    }
 
272
    
 
273
    /**
 
274
     */
 
275
    private void displayMultipleItemsSelected() {
 
276
        if (msgMultipleFilesSelected == null) {
 
277
            msgMultipleFilesSelected = NbBundle.getMessage(
 
278
                                            getClass(),
 
279
                                            "MsgMultipleFilesSelected");//NOI18N
 
280
        }
 
281
        displayMessage(msgMultipleFilesSelected);
 
282
    }
 
283
    
 
284
    /**
 
285
     */
 
286
    private void displayMessage(String message) {
 
287
        lblMessage.setText(message);
 
288
        if (displayedCard != MESSAGE_VIEW) {
 
289
            cardLayout.show(this, displayedCard = MESSAGE_VIEW);
 
290
        }
 
291
    }
 
292
    
 
293
    /**
 
294
     * @author  Tim Boudreau
 
295
     * @author  Marian Petras
 
296
     */
 
297
    private void displayFile(final MatchingObject matchingObj,
 
298
                             final int partIndex) {
 
299
        assert EventQueue.isDispatchThread();
 
300
        
 
301
        synchronized (displayer) {          //PENDING - review synchronization
 
302
            if (task != null) {
 
303
                task.cancel();
 
304
                task = null;
 
305
            }
 
306
            
 
307
            final Item item = new Item(resultModel, matchingObj, partIndex);
 
308
            
 
309
            MatchingObject.InvalidityStatus invalidityStatus
 
310
                                            = matchingObj.checkValidity();
 
311
            if (invalidityStatus != null) {
 
312
                displayMessage(invalidityStatus.getDescription(
 
313
                                            matchingObj.getFile().getPath()));
 
314
                return;
 
315
            }
 
316
            
 
317
            requestText(item, displayer);
 
318
            String description = matchingObj.getDescription();
 
319
            lblPath.setText(description);
 
320
            lblPath.setToolTipText(description);        //in case it doesn't fit
 
321
        }
 
322
    }
 
323
    
 
324
    /**
 
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.
 
329
     * 
 
330
     * @param  item  item to be displayed by the text displayer
 
331
     * @param  textDisplayer  displayer that should display the item
 
332
     * 
 
333
     * @author  Tim Boudreau
 
334
     */
 
335
    private void requestText(Item item, TextDisplayer textDisplayer) {
 
336
        assert EventQueue.isDispatchThread();
 
337
        
 
338
        synchronized (this) {           //PENDING - review synchronization
 
339
            if (textFetcher != null) {
 
340
                if (textFetcher.replaceLocation(item, textDisplayer)) {
 
341
                    return;
 
342
                } else {
 
343
                    textFetcher.cancel();
 
344
                    textFetcher = null;
 
345
                }
 
346
            }
 
347
            if (textFetcher == null) {
 
348
                textFetcher = new TextFetcher(item,
 
349
                                              textDisplayer,
 
350
                                              requestProcessor);
 
351
            }
 
352
        }
 
353
    }
 
354
 
 
355
    /**
 
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.
 
360
     * 
 
361
     * @see  TextReceiver
 
362
     * @author  Tim Boudreau
 
363
     * @author  Marian Petras
 
364
     */
 
365
    private class Displayer implements TextDisplayer, Runnable {
 
366
        
 
367
        private TextDetail location;
 
368
        
 
369
        /**
 
370
         * @author  Tim Boudreau
 
371
         */
 
372
        public void setText(final String text,
 
373
                            String mimeType,
 
374
                            final TextDetail location) {
 
375
            assert EventQueue.isDispatchThread();
 
376
            
 
377
            if ("content/unknown".equals(mimeType)) {                   //NOI18N
 
378
                mimeType = "text/plain";  //Good idea? Bad? Hmm...      //NOI18N
 
379
            }
 
380
            
 
381
            /*
 
382
             * Changing content type clears the text - so the content type
 
383
             * (in this case, MIME-type only) must be set _before_ the text
 
384
             * is set.
 
385
             */
 
386
            if ((editorMimeType == null) || !editorMimeType.equals(mimeType)) {
 
387
                editorPane.setContentType(mimeType);
 
388
                editorMimeType = mimeType;
 
389
            }
 
390
            editorPane.setText(text);
 
391
            
 
392
            if (displayedCard != FILE_VIEW) {
 
393
                cardLayout.show(ContextView.this, displayedCard = FILE_VIEW);
 
394
            }
 
395
            
 
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);
 
402
            } else {
 
403
                scrollToTop();
 
404
            }
 
405
        }
 
406
 
 
407
        /**
 
408
         * 
 
409
         * @author  Tim Boudreau
 
410
         * @author  Marian Petras
 
411
         */
 
412
        public void run() {
 
413
            assert EventQueue.isDispatchThread();
 
414
            
 
415
            boolean scrolled = false;
 
416
            try {
 
417
                if (!editorPane.isShowing()) {
 
418
                    return;
 
419
                }
 
420
                
 
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);
 
434
                        if (r != null) {
 
435
                            //Editor kit not yet updated, what to do
 
436
                            editorPane.scrollRectToVisible(r);
 
437
                            scrolled = true;
 
438
                        }
 
439
                    }
 
440
                    editorPane.getCaret().setBlinkRate(0);
 
441
                    editorPane.repaint();
 
442
                }
 
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);
 
449
            }
 
450
            if (!scrolled) {
 
451
                scrollToTop();
 
452
            }
 
453
        }
 
454
        
 
455
        /**
 
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.
 
460
         *
 
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
 
464
         * 
 
465
         * @author  Marian Petras
 
466
         */
 
467
        private int getCursorOffset(StyledDocument doc, int line) {
 
468
            assert EventQueue.isDispatchThread();
 
469
            assert line >= 0;
 
470
 
 
471
            try {
 
472
                return NbDocument.findLineOffset(doc, line);
 
473
            } catch (IndexOutOfBoundsException ex) {
 
474
                /* probably line number out of bounds */
 
475
 
 
476
                Element lineRootElement = NbDocument.findLineRootElement(doc);
 
477
                int lineCount = lineRootElement.getElementCount();
 
478
                if (line >= lineCount) {
 
479
                    return NbDocument.findLineOffset(doc, lineCount - 1);
 
480
                } else {
 
481
                    throw ex;
 
482
                }
 
483
            }
 
484
        }
 
485
    
 
486
        /**
 
487
         */
 
488
        private void scrollToTop() {
 
489
            JScrollBar scrollBar;
 
490
 
 
491
            scrollBar = editorScroll.getHorizontalScrollBar();
 
492
            scrollBar.setValue(scrollBar.getMinimum());
 
493
 
 
494
            scrollBar = editorScroll.getVerticalScrollBar();
 
495
            scrollBar.setValue(scrollBar.getMinimum());
 
496
        }
 
497
        
 
498
    }
 
499
 
 
500
}