KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > output2 > ui > AbstractOutputPane


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 /*
20  * OutputPane.java
21  *
22  * Created on May 14, 2004, 6:45 PM
23  */

24
25 package org.netbeans.core.output2.ui;
26
27 import java.awt.Rectangle JavaDoc;
28 import javax.swing.plaf.TextUI JavaDoc;
29
30 import javax.swing.*;
31 import javax.swing.event.ChangeEvent JavaDoc;
32 import javax.swing.event.ChangeListener JavaDoc;
33 import javax.swing.event.DocumentEvent JavaDoc;
34 import javax.swing.event.DocumentListener JavaDoc;
35 import javax.swing.text.*;
36 import java.awt.*;
37 import java.awt.event.*;
38 import org.openide.util.Exceptions;
39
40 /**
41  * A scroll pane containing an editor pane, with special handling of the caret
42  * and scrollbar - until a keyboard or mouse event, after a call to setDocument(),
43  * the caret and scrollbar are locked to the last line of the document. This avoids
44  * "jumping" scrollbars as the position of the caret (and thus the scrollbar) get updated
45  * to reposition them at the bottom of the document on every document change.
46  *
47  * @author Tim Boudreau
48  */

49 public abstract class AbstractOutputPane extends JScrollPane implements DocumentListener JavaDoc, MouseListener, MouseMotionListener, KeyListener, ChangeListener JavaDoc, MouseWheelListener, Runnable JavaDoc {
50     private boolean locked = true;
51     
52     private int fontHeight = -1;
53     private int fontWidth = -1;
54     protected JEditorPane textView;
55     int lastCaretLine = 0;
56     boolean hadSelection = false;
57     boolean recentlyReset = false;
58
59     public AbstractOutputPane() {
60         textView = createTextView();
61         init();
62     }
63
64     public void requestFocus() {
65         textView.requestFocus();
66     }
67     
68     public boolean requestFocusInWindow() {
69         return textView.requestFocusInWindow();
70     }
71     
72     protected abstract JEditorPane createTextView();
73
74     protected void documentChanged() {
75         lastLength = -1;
76         if (pendingCaretLine != -1) {
77             if (!sendCaretToLine (pendingCaretLine, pendingCaretSelect)) {
78                 ensureCaretPosition();
79             }
80         } else {
81             ensureCaretPosition();
82         }
83         if (recentlyReset && isShowing()) {
84             recentlyReset = false;
85         }
86         if (locked) {
87             setMouseLine(-1);
88         }
89         if (isWrapped()) {
90             //Saves having OutputEditorKit have to do its own listening
91
getViewport().revalidate();
92             getViewport().repaint();
93         }
94     }
95     
96     public abstract boolean isWrapped();
97     public abstract void setWrapped (boolean val);
98
99     public boolean hasSelection() {
100         return textView.getSelectionStart() != textView.getSelectionEnd();
101     }
102
103     boolean isScrollLocked() {
104         return locked;
105     }
106
107     /**
108      * Ensure that the document is scrolled all the way to the bottom (unless
109      * some user event like scrolling or placing the caret has unlocked it).
110      * <p>
111      * Note that this method is always called on the event queue, since
112      * OutputDocument only fires changes on the event queue.
113      */

114     public final void ensureCaretPosition() {
115         if (locked) {
116             //Make sure the scrollbar is updated *after* the document change
117
//has been processed and the scrollbar model's maximum updated
118
if (!enqueued) {
119                 SwingUtilities.invokeLater(this);
120                 enqueued = true;
121             }
122         }
123     }
124     
125     /** True when invokeLater has already been called on this instance */
126     private boolean enqueued = false;
127     /**
128      * Scrolls the pane to the bottom, invokeLatered to ensure all state has
129      * been updated, so the bottom really *is* the bottom.
130      */

131     public void run() {
132         enqueued = false;
133         getVerticalScrollBar().setValue(getVerticalScrollBar().getModel().getMaximum());
134         getHorizontalScrollBar().setValue(getHorizontalScrollBar().getModel().getMinimum());
135     }
136
137     public int getSelectionStart() {
138         return textView.getSelectionStart();
139     }
140     
141     public int getSelectionEnd() {
142         return textView.getSelectionEnd();
143     }
144
145     public void setSelection (int start, int end) {
146         int rstart = Math.min (start, end);
147         int rend = Math.max (start, end);
148         if (rstart == rend) {
149             getCaret().setDot(rstart);
150         } else {
151             textView.setSelectionStart(rstart);
152             textView.setSelectionEnd(rend);
153         }
154     }
155
156     public void selectAll() {
157         unlockScroll();
158         getCaret().setVisible(true);
159         textView.setSelectionStart(0);
160         textView.setSelectionEnd(getLength());
161     }
162
163     public boolean isAllSelected() {
164         return textView.getSelectionStart() == 0 && textView.getSelectionEnd() == getLength();
165     }
166
167     protected void init() {
168         setViewportView(textView);
169         textView.setEditable(false);
170
171         textView.addMouseListener(this);
172         textView.addMouseWheelListener(this);
173         textView.addMouseMotionListener(this);
174         textView.addKeyListener(this);
175         textView.setCaret (new OCaret());
176         
177         getCaret().setVisible(true);
178         getCaret().setBlinkRate(0);
179         getCaret().setSelectionVisible(true);
180         
181         getVerticalScrollBar().getModel().addChangeListener(this);
182         getVerticalScrollBar().addMouseMotionListener(this);
183         
184         getViewport().addMouseListener(this);
185         getVerticalScrollBar().addMouseListener(this);
186         setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
187         setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
188         addMouseListener(this);
189
190         getCaret().addChangeListener(this);
191         Integer JavaDoc i = (Integer JavaDoc) UIManager.get("customFontSize"); //NOI18N
192
int size;
193         if (i != null) {
194             size = i.intValue();
195         } else {
196             Font f = (Font) UIManager.get("controlFont");
197             size = f != null ? f.getSize() : 11;
198         }
199         textView.setFont (new Font ("Monospaced", Font.PLAIN, size)); //NOI18N
200
setBorder (BorderFactory.createEmptyBorder());
201         setViewportBorder (BorderFactory.createEmptyBorder());
202         
203         Color c = UIManager.getColor("nb.output.selectionBackground");
204         if (c != null) {
205             textView.setSelectionColor(c);
206         }
207     }
208
209     public final Document getDocument() {
210         return textView.getDocument();
211     }
212     
213     /**
214      * This method is here for use *only* by unit tests.
215      */

216     public final JTextComponent getTextView() {
217         return textView;
218     }
219
220     public final void copy() {
221         if (getCaret().getDot() != getCaret().getMark()) {
222             textView.copy();
223         } else {
224             Toolkit.getDefaultToolkit().beep();
225         }
226     }
227
228     protected void setDocument (Document doc) {
229         if (hasSelection()) {
230             hasSelectionChanged(false);
231         }
232         hadSelection = false;
233         lastCaretLine = 0;
234         lastLength = -1;
235         Document old = textView.getDocument();
236         old.removeDocumentListener(this);
237         if (doc != null) {
238             textView.setDocument(doc);
239             doc.addDocumentListener(this);
240             lockScroll();
241             recentlyReset = true;
242             pendingCaretLine = -1;
243         } else {
244             textView.setDocument (new PlainDocument());
245             textView.setEditorKit(new DefaultEditorKit());
246         }
247     }
248     
249     protected void setEditorKit(EditorKit kit) {
250         Document doc = textView.getDocument();
251         
252         textView.setEditorKit(kit);
253         textView.setDocument(doc);
254         updateKeyBindings();
255         getCaret().setVisible(true);
256         getCaret().setBlinkRate(0);
257     }
258     
259     /**
260      * Setting the editor kit will clear the action map/key map connection
261      * to the TopComponent, so we reset it here.
262      */

263     protected final void updateKeyBindings() {
264         Keymap keymap = textView.getKeymap();
265         keymap.removeKeyStrokeBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
266     }
267     
268     protected EditorKit getEditorKit() {
269         return textView.getEditorKit();
270     }
271     
272     public final int getLineCount() {
273         return textView.getDocument().getDefaultRootElement().getElementCount();
274     }
275
276     private int lastLength = -1;
277     public final int getLength() {
278         if (lastLength == -1) {
279             lastLength = textView.getDocument().getLength();
280         }
281         return lastLength;
282     }
283     
284     /**
285      * If we are sending the caret to a hyperlinked line, but it is < 3 lines
286      * from the bottom, we will hold the line number in this field until there
287      * are enough lines that it will be semi-centered.
288      */

289     private int pendingCaretLine = -1;
290     private boolean pendingCaretSelect = false;
291     private boolean inSendCaretToLine = false;
292     
293     public final boolean sendCaretToLine(int idx, boolean select) {
294         int count = getLineCount();
295         if (count - idx < 3) {
296             pendingCaretLine = idx;
297             pendingCaretSelect = select;
298             return false;
299         } else {
300             inSendCaretToLine = true;
301             pendingCaretLine = -1;
302             unlockScroll();
303             getCaret().setVisible(true);
304             getCaret().setSelectionVisible(true);
305             Element el = textView.getDocument().getDefaultRootElement().getElement(Math.min(idx, getLineCount() - 1));
306             int position = el.getStartOffset();
307             if (select) {
308                 getCaret().setDot (el.getEndOffset()-1);
309                 getCaret().moveDot (position);
310                 getCaret().setSelectionVisible(true);
311                 textView.repaint();
312             } else {
313                 getCaret().setDot(position);
314             }
315             if (idx + 3 < getLineCount()) {
316                 try {
317                     Rectangle JavaDoc r = textView.modelToView(textView.getDocument().getDefaultRootElement().getElement(idx + 3).getStartOffset());
318                     if (r != null) { //Will be null if maximized - no parent, no coordinate space
319
textView.scrollRectToVisible(r);
320                     }
321                 } catch (BadLocationException ble) {
322                     Exceptions.printStackTrace(ble);
323                 }
324             }
325             inSendCaretToLine = false;
326             return true;
327         }
328     }
329
330
331     public final void lockScroll() {
332         if (!locked) {
333             locked = true;
334         }
335     }
336     
337     public final void unlockScroll() {
338         if (locked) {
339             locked = false;
340         }
341     }
342
343     protected abstract void caretEnteredLine (int line);
344     
345     protected abstract void lineClicked (int line, Point p);
346     
347     protected abstract void postPopupMenu (Point p, Component src);
348     
349     public final int getCaretLine() {
350         int result = -1;
351         int charPos = getCaret().getDot();
352         if (charPos > 0) {
353             result = textView.getDocument().getDefaultRootElement().getElementIndex(charPos);
354         }
355         return result;
356     }
357
358     public final int getCaretPos() {
359         return getCaret().getDot();
360     }
361
362     public final void paint (Graphics g) {
363         if (fontHeight == -1) {
364             fontHeight = g.getFontMetrics(textView.getFont()).getHeight();
365             fontWidth = g.getFontMetrics(textView.getFont()).charWidth('m'); //NOI18N
366
}
367         super.paint(g);
368     }
369
370 //***********************Listener implementations*****************************
371

372     public void stateChanged(ChangeEvent JavaDoc e) {
373         if (e.getSource() instanceof JViewport) {
374             if (locked) {
375                 ensureCaretPosition();
376             }
377         } else if (e.getSource() == getVerticalScrollBar().getModel()) {
378             if (!locked) { //XXX check if doc is still being written?
379
BoundedRangeModel mdl = getVerticalScrollBar().getModel();
380                 if (mdl.getValue() + mdl.getExtent() == mdl.getMaximum()) {
381                     lockScroll();
382                 }
383             }
384         } else {
385             if (!locked) {
386                 maybeSendCaretEnteredLine();
387             }
388             boolean hasSelection = textView.getSelectionStart() != textView.getSelectionEnd();
389             if (hasSelection != hadSelection) {
390                 hadSelection = hasSelection;
391                 hasSelectionChanged (hasSelection);
392             }
393         }
394     }
395
396     private boolean caretLineChanged() {
397         int line = getCaretLine();
398         boolean result = line != lastCaretLine;
399         lastCaretLine = line;
400         return result;
401     }
402
403     private void maybeSendCaretEnteredLine() {
404         if (EventQueue.getCurrentEvent() instanceof MouseEvent) {
405             //User may have clicked a hyperlink, in which case, we'll test
406
//it and see if it's really in the text of the hyperlink - so
407
//don't do anything here
408
return;
409         }
410         //Don't message the controller if we're programmatically setting
411
//the selection, or if the caret moved because output was written -
412
//it can cause the controller to send events to OutputListeners which
413
//should only happen for user events
414
if ((!locked && caretLineChanged()) && !inSendCaretToLine) {
415             int line = getCaretLine();
416             boolean sel = textView.getSelectionStart() != textView.getSelectionEnd();
417             if (line != -1 && !sel) {
418                 caretEnteredLine(getCaretLine());
419             }
420             if (sel != hadSelection) {
421                 hadSelection = sel;
422                 hasSelectionChanged (sel);
423             }
424         }
425     }
426
427
428     private void hasSelectionChanged(boolean sel) {
429         ((AbstractOutputTab) getParent()).hasSelectionChanged(sel);
430     }
431
432     public final void changedUpdate(DocumentEvent JavaDoc e) {
433         //Ensure it is consumed
434
e.getLength();
435         documentChanged();
436     }
437
438     public final void insertUpdate(DocumentEvent JavaDoc e) {
439         //Ensure it is consumed
440
e.getLength();
441         documentChanged();
442     }
443
444     public final void removeUpdate(DocumentEvent JavaDoc e) {
445         //Ensure it is consumed
446
e.getLength();
447         documentChanged();
448     }
449
450     public void mouseClicked(MouseEvent e) {
451     }
452
453     public void mouseEntered(MouseEvent e) {
454     }
455
456     public void mouseExited(MouseEvent e) {
457         setMouseLine (-1);
458     }
459
460     private int mouseLine = -1;
461     public void setMouseLine (int line, Point p) {
462         if (mouseLine != line) {
463             mouseLine = line;
464         }
465     }
466     
467     public final void setMouseLine (int line) {
468         setMouseLine (line, null);
469     }
470
471
472     public void mouseMoved(MouseEvent e) {
473         Point p = e.getPoint();
474         int pos = textView.viewToModel(p);
475         if (pos < getLength()) {
476             int line = getDocument().getDefaultRootElement().getElementIndex(pos);
477             int lineStart = getDocument().getDefaultRootElement().getElement(line).getStartOffset();
478             int lineLength = getDocument().getDefaultRootElement().getElement(line).getEndOffset() -
479                     lineStart;
480
481             try {
482                 Rectangle JavaDoc r = textView.modelToView(lineStart + lineLength -1);
483                 int maxX = r.x + r.width;
484                 boolean inLine = p.x <= maxX;
485                 if (isWrapped()) {
486                     Rectangle JavaDoc ra = textView.modelToView(lineStart);
487                     if (ra.y <= r.y) {
488                         if (p.y < r.y) {
489                             inLine = true;
490                         }
491                     }
492                 }
493                 
494                 if (inLine) {
495                     setMouseLine (line, p);
496                 } else {
497                     setMouseLine(-1);
498                 }
499             } catch (BadLocationException ble) {
500                 setMouseLine(-1);
501             }
502         }
503     }
504
505     public void mouseDragged(MouseEvent e) {
506         if (e.getSource() == getVerticalScrollBar()) {
507             int y = e.getY();
508             if (y > getVerticalScrollBar().getHeight()) {
509                 lockScroll();
510             }
511         }
512     }
513     
514     public void mousePressed(MouseEvent e) {
515         if (locked && !e.isPopupTrigger()) {
516             Element el = getDocument().getDefaultRootElement().getElement(getLineCount()-1);
517             getCaret().setDot(el.getStartOffset());
518             unlockScroll();
519             //We should now set the caret position so the caret doesn't
520
//seem to ignore the first click
521
if (e.getSource() == textView) {
522                 getCaret().setDot (textView.viewToModel(e.getPoint()));
523             }
524         }
525         if (e.isPopupTrigger()) {
526             //Convert immediately to our component space - if the
527
//text view scrolls before the component is opened, popup can
528
//appear above the top of the screen
529
Point p = SwingUtilities.convertPoint((Component) e.getSource(),
530                 e.getPoint(), this);
531             
532             postPopupMenu (p, this);
533         }
534     }
535
536     public final void mouseReleased(MouseEvent e) {
537         if (e.getSource() == textView && SwingUtilities.isLeftMouseButton(e)) {
538             int pos = textView.viewToModel(e.getPoint());
539             if (pos != -1) {
540                 int line = textView.getDocument().getDefaultRootElement().getElementIndex(pos);
541                 if (line >= 0) {
542                     lineClicked(line, e.getPoint());
543                     e.consume(); //do NOT allow this window's caret to steal the focus from editor window
544
}
545             }
546         }
547         if (e.isPopupTrigger()) {
548             Point p = SwingUtilities.convertPoint((Component) e.getSource(),
549             //Convert immediately to our component space - if the
550
//text view scrolls before the component is opened, popup can
551
//appear above the top of the screen
552
e.getPoint(), this);
553             
554             postPopupMenu (p, this);
555         }
556     }
557     
558     public void keyPressed(KeyEvent keyEvent) {
559         if (keyEvent.getKeyCode() == KeyEvent.VK_END) {
560             lockScroll();
561         } else {
562             unlockScroll();
563         }
564     }
565
566     public void keyReleased(KeyEvent keyEvent) {
567     }
568
569     public void keyTyped(KeyEvent keyEvent) {
570     }
571
572     public final void mouseWheelMoved(MouseWheelEvent e) {
573         BoundedRangeModel sbmodel = getVerticalScrollBar().getModel();
574         int max = sbmodel.getMaximum();
575         int range = sbmodel.getExtent();
576
577         int currPosition = sbmodel.getValue();
578         if (e.getSource() == textView) {
579             int newPosition = Math.max (0, Math.min (sbmodel.getMaximum(),
580                 currPosition + (e.getUnitsToScroll() * textView.getFontMetrics(textView.getFont()).getHeight())));
581             // height is a magic constant because of #57532
582
sbmodel.setValue (newPosition);
583             if (newPosition + range >= max) {
584                 lockScroll();
585                 return;
586             }
587         }
588         unlockScroll();
589     }
590
591     Caret getCaret() {
592         return textView.getCaret();
593     }
594     
595     private class OCaret extends DefaultCaret {
596         public void setSelectionVisible(boolean val) {
597             super.setSelectionVisible(true);
598             super.setBlinkRate(0);
599         }
600         public boolean isSelectionVisible() {
601             return true;
602         }
603         public void setBlinkRate(int rate) {
604             super.setBlinkRate(0);
605         }
606  
607         public boolean isVisible() { return true; }
608         
609         public void paint(Graphics g) {
610             JTextComponent component = textView;
611             if(isVisible() && y >= 0) {
612                 try {
613                     TextUI JavaDoc mapper = component.getUI();
614                     Rectangle JavaDoc r = mapper.modelToView(component, getDot(), Position.Bias.Forward);
615
616                     if ((r == null) || ((r.width == 0) && (r.height == 0))) {
617                         return;
618                     }
619                     if (width > 0 && height > 0 &&
620                                     !this._contains(r.x, r.y, r.width, r.height)) {
621                         // We seem to have gotten out of sync and no longer
622
// contain the right location, adjust accordingly.
623
Rectangle JavaDoc clip = g.getClipBounds();
624
625                         if (clip != null && !clip.contains(this)) {
626                             // Clip doesn't contain the old location, force it
627
// to be repainted lest we leave a caret around.
628
repaint();
629                         }
630  // System.err.println("WRONG! Caret dot m2v = " + r + " but my bounds are " + x + "," + y + "," + width + "," + height);
631

632                         // This will potentially cause a repaint of something
633
// we're already repainting, but without changing the
634
// semantics of damage we can't really get around this.
635
damage(r);
636                     }
637                     g.setColor(component.getCaretColor());
638                     g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
639
640                 } catch (BadLocationException e) {
641                     // can't render I guess
642
// System.err.println("Can't render cursor");
643
}
644             }
645         }
646         
647         private boolean _contains(int X, int Y, int W, int H) {
648             int w = this.width;
649             int h = this.height;
650             if ((w | h | W | H) < 0) {
651                 // At least one of the dimensions is negative...
652
return false;
653             }
654             // Note: if any dimension is zero, tests below must return false...
655
int x = this.x;
656             int y = this.y;
657             if (X < x || Y < y) {
658                 return false;
659             }
660             if (W > 0) {
661                 w += x;
662                 W += X;
663                 if (W <= X) {
664                     // X+W overflowed or W was zero, return false if...
665
// either original w or W was zero or
666
// x+w did not overflow or
667
// the overflowed x+w is smaller than the overflowed X+W
668
if (w >= x || W > w) {
669                         return false;
670                     }
671                 } else {
672                     // X+W did not overflow and W was not zero, return false if...
673
// original w was zero or
674
// x+w did not overflow and x+w is smaller than X+W
675
if (w >= x && W > w) {
676                         //This is the bug in DefaultCaret - returns false here
677
return true;
678                     }
679                 }
680             }
681             else if ((x + w) < X) {
682                 return false;
683             }
684             if (H > 0) {
685                 h += y;
686                 H += Y;
687                 if (H <= Y) {
688                     if (h >= y || H > h) return false;
689                 } else {
690                     if (h >= y && H > h) return false;
691                 }
692             }
693             else if ((y + h) < Y) {
694                 return false;
695             }
696             return true;
697         }
698
699         public void mouseReleased(MouseEvent e) {
700             if( !e.isConsumed() )
701                 super.mouseReleased(e);
702         }
703     }
704 }
705
Popular Tags