KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > GlyphGutter


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 package org.netbeans.editor;
21
22 import javax.swing.JComponent JavaDoc;
23 import java.awt.Dimension JavaDoc;
24 import java.awt.Rectangle JavaDoc;
25 import java.awt.Graphics JavaDoc;
26 import java.awt.Color JavaDoc;
27 import java.awt.Font JavaDoc;
28 import java.awt.Image JavaDoc;
29 import java.net.URL JavaDoc;
30 import java.net.MalformedURLException JavaDoc;
31 import java.awt.Toolkit JavaDoc;
32 import java.awt.event.MouseAdapter JavaDoc;
33 import java.awt.event.MouseEvent JavaDoc;
34 import javax.swing.event.DocumentListener JavaDoc;
35 import javax.swing.event.DocumentEvent JavaDoc;
36 import javax.swing.text.BadLocationException JavaDoc;
37 import java.awt.FontMetrics JavaDoc;
38 import java.awt.Insets JavaDoc;
39 import java.awt.Point JavaDoc;
40 import java.awt.Shape JavaDoc;
41 import java.awt.image.ImageObserver JavaDoc;
42 import java.awt.event.InputEvent JavaDoc;
43 import javax.swing.JPopupMenu JavaDoc;
44 import org.netbeans.editor.Utilities;
45 import javax.swing.event.PopupMenuListener JavaDoc;
46 import javax.swing.event.PopupMenuEvent JavaDoc;
47 import org.netbeans.editor.FontMetricsCache;
48 import java.beans.PropertyChangeListener JavaDoc;
49 import java.beans.PropertyChangeEvent JavaDoc;
50 import java.awt.event.*;
51 import java.lang.ref.WeakReference JavaDoc;
52 import java.util.Map JavaDoc;
53 import javax.swing.Action JavaDoc;
54 import javax.accessibility.*;
55 import javax.swing.SwingUtilities JavaDoc;
56 import javax.swing.text.AbstractDocument JavaDoc;
57 import javax.swing.text.BoxView JavaDoc;
58 import javax.swing.text.Element JavaDoc;
59 import javax.swing.text.JTextComponent JavaDoc;
60 import javax.swing.text.Position JavaDoc;
61 import javax.swing.text.View JavaDoc;
62 import org.netbeans.api.editor.fold.FoldHierarchy;
63 import org.netbeans.api.editor.fold.FoldHierarchyEvent;
64 import org.netbeans.api.editor.fold.FoldHierarchyListener;
65 import org.openide.ErrorManager;
66 import org.openide.util.NbBundle;
67
68 /** GlyphGutter is component for displaying line numbers and annotation
69  * glyph icons. Component also allow to "cycle" through the annotations. It
70  * means that if there is more than one annotation on the line, only one of them
71  * might be visible. And clicking the special cycling button in the gutter the user
72  * can cycle through the annotations.
73  *
74  * @author David Konecny
75  * @since 07/2001
76  */

77
78 public class GlyphGutter extends JComponent JavaDoc implements Annotations.AnnotationsListener, Accessible, SettingsChangeListener, SideBarFactory {
79
80     /** EditorUI which part this gutter is */
81     private EditorUI editorUI;
82     
83     /** Document to which this gutter is attached*/
84     private BaseDocument doc;
85     
86     /** Annotations manager responsible for annotations for this line */
87     private Annotations annos;
88     
89     /** Cycling button image */
90     private Image JavaDoc gutterButton;
91     
92     /** Backroung color of the gutter */
93     private Color JavaDoc backgroundColor;
94     
95     /** Foreground color of the gutter. Used for drawing line numbers. */
96     private Color JavaDoc foreColor;
97     
98     /** Font used for drawing line numbers */
99     private Font JavaDoc font;
100     
101     /** Height of the line as it was calculated in EditorUI. */
102     private int lineHeight;
103
104     /** Flag whther the gutter was initialized or not. The painting is disabled till the
105      * gutter is not initialized */

106     private boolean init;
107     
108     /** Width of the glyph gutter*/
109     private int glyphGutterWidth;
110
111     /** Predefined width of the glyph icons */
112     private final static int glyphWidth = 16;
113
114     /** Preddefined width of the cycling button */
115     private final static int glyphButtonWidth = 9;
116     
117     /** Predefined left area width - area between left border of the number
118      * and the left border of the glyphgutter
119      */

120     private final static int leftGap= 10;
121
122     /** Predefined right area width - area between right border of the number
123      * and the right border of the glyphgutter
124      */

125     private final static int rightGap= 4;
126     
127     /** Whether the line numbers are shown or not */
128     private boolean showLineNumbers = true;
129     
130     /** Image observer used for glyph icons */
131     private ImageObserver JavaDoc imgObserver = null;
132
133     /** The gutter height is enlarged by number of lines which specifies this constant */
134     private static final int ENLARGE_GUTTER_HEIGHT = 300;
135     
136     /** The hightest line number. This value is used for calculating width of the gutter */
137     private int highestLineNumber = 0;
138     
139     /** Whether the annotation glyph can be drawn over the line numbers */
140     private boolean drawOverLineNumbers = false;
141
142     /* These two variables are used for caching of count of line annos
143      * on the line over which is the mouse caret. Just for sake of optimalization. */

144     private int cachedCountOfAnnos = -1;
145     private int cachedCountOfAnnosForLine = -1;
146
147     /** Property change listener on AnnotationTypes changes */
148     private PropertyChangeListener JavaDoc annoTypesListener;
149     private PropertyChangeListener JavaDoc editorUIListener;
150     private GlyphGutter.GlyphGutterFoldHierarchyListener glyphGutterFoldHierarchyListener;
151     private GutterMouseListener gutterMouseListener;
152     private FoldHierarchy foldHierarchy;
153     private Map JavaDoc renderingHints;
154     
155     public GlyphGutter(){}
156     
157     public GlyphGutter(EditorUI editorUI) {
158         super();
159         this.editorUI = editorUI;
160         init = false;
161         doc = editorUI.getDocument();
162         annos = doc.getAnnotations();
163         
164         // Annotations class is model for this view, so the listener on changes in
165
// Annotations must be added here
166
annos.addAnnotationsListener(this);
167
168         // do initialization
169
init();
170         update();
171         Settings.addSettingsChangeListener(this);
172         setMaximumSize(new Dimension JavaDoc(Integer.MAX_VALUE, Integer.MAX_VALUE));
173         foldHierarchy = FoldHierarchy.get(editorUI.getComponent());
174         glyphGutterFoldHierarchyListener = new GlyphGutterFoldHierarchyListener();
175         foldHierarchy.addFoldHierarchyListener(glyphGutterFoldHierarchyListener);
176         editorUIListener = new EditorUIListener();
177         editorUI.addPropertyChangeListener(editorUIListener);
178         updateRenderingHints();
179         setOpaque (true);
180     }
181
182     private void updateRenderingHints(){
183         JTextComponent JavaDoc comp = editorUI.getComponent();
184         if (comp == null) return;
185         Object JavaDoc value = (Map JavaDoc)(Toolkit.getDefaultToolkit().getDesktopProperty(
186                 "awt.font.desktophints")); //NOI18N
187
//Don't bother seeing if the hints are explicitly turned off (if they
188
//even can be) as in EditorUI - it's a tooltip, desktop default is
189
//fine
190
if (value == null) {
191             value = Settings.getValue(Utilities.getKitClass(comp),
192                     SettingsNames.RENDERING_HINTS);
193         }
194         renderingHints = (value instanceof Map JavaDoc) ? (java.util.Map JavaDoc)value : null;
195     }
196     
197     public void settingsChange(SettingsChangeEvent evt) {
198         if (editorUI == null) // no long er active
199
return;
200
201         JTextComponent JavaDoc component = editorUI.getComponent();
202         if (evt == null || component == null) return;
203
204         String JavaDoc settingName = evt.getSettingName();
205         if (settingName == null || SettingsNames.RENDERING_HINTS.equals(settingName)) {
206             updateRenderingHints();
207         }
208         
209         Class JavaDoc kitClass = evt.getKitClass();
210         if (Utilities.getKitClass(component) != kitClass){
211             Rectangle JavaDoc rect = component.getVisibleRect();
212             if (rect!=null && rect.width == 0){
213                 if (SwingUtilities.isEventDispatchThread()) {
214                     resize();
215                 } else {
216                     SwingUtilities.invokeLater(
217                         new Runnable JavaDoc() {
218                             public void run() {
219                                 resize();
220                             }
221                         }
222                     );
223                 }
224             }
225         }
226     }
227     
228     
229     /* Read accessible context
230      * @return - accessible context
231      */

232     public AccessibleContext getAccessibleContext () {
233         if (accessibleContext == null) {
234             accessibleContext = new AccessibleJComponent() {
235                 public AccessibleRole getAccessibleRole() {
236                     return AccessibleRole.PANEL;
237                 }
238             };
239         }
240         return accessibleContext;
241     }
242
243     /** Do initialization of the glyph gutter*/
244     protected void init() {
245         if (editorUI == null)
246             return ;
247
248         URL JavaDoc imageURL = null;
249
250         try {
251             // cycling button
252
imageURL = new URL JavaDoc("nbresloc:/org/netbeans/editor/resources/glyphbutton.gif"); // NOI18N
253
} catch (MalformedURLException JavaDoc ex) {
254             Utilities.annotateLoggable(ex);
255             return;
256         }
257         
258         if (imageURL != null)
259             gutterButton = Toolkit.getDefaultToolkit().getImage(imageURL);
260
261         setToolTipText ("");
262         getAccessibleContext().setAccessibleName(NbBundle.getBundle(BaseKit.class).getString("ACSN_Glyph_Gutter")); // NOI18N
263
getAccessibleContext().setAccessibleDescription(NbBundle.getBundle(BaseKit.class).getString("ACSD_Glyph_Gutter")); // NOI18N
264

265         // add mouse listener for cycling button
266
// TODO: clicking the line number should select whole line
267
// TODO: clicking the line number abd dragging the mouse should select block of lines
268
gutterMouseListener = new GutterMouseListener ();
269         addMouseListener (gutterMouseListener);
270         addMouseMotionListener (gutterMouseListener);
271
272         // after the glyph icons are loaded it is necessary to repaint the gutter
273
imgObserver = new Observer JavaDoc(this);
274         
275         AnnotationTypes.getTypes().addPropertyChangeListener( annoTypesListener = new PropertyChangeListener JavaDoc() {
276             public void propertyChange (PropertyChangeEvent JavaDoc evt) {
277                 if (evt.getPropertyName() == AnnotationTypes.PROP_GLYPHS_OVER_LINE_NUMBERS ||
278                     evt.getPropertyName() == AnnotationTypes.PROP_SHOW_GLYPH_GUTTER) {
279                     update();
280                 }
281             }
282         });
283         
284     }
285     
286     /** Update colors, fonts, sizes and invalidate itself. This method is
287      * called from EditorUI.update() */

288     public void update() {
289         if (editorUI == null)
290             return ;
291         Coloring lineColoring = (Coloring)editorUI.getColoringMap().get(SettingsNames.LINE_NUMBER_COLORING);
292         Coloring defaultColoring = (Coloring)editorUI.getDefaultColoring();
293         
294         // fix for issue #16940
295
// the real cause of this problem is that closed document is not garbage collected,
296
// because of *some* references (see #16072) and so any change in AnnotationTypes.PROP_*
297
// properties is fired which must update this component although it is not visible anymore
298
if (lineColoring == null)
299             return;
300         
301         if (lineColoring.getBackColor() != null)
302             backgroundColor = lineColoring.getBackColor();
303         else
304             backgroundColor = defaultColoring.getBackColor();
305
306         if (lineColoring.getForeColor() != null)
307             foreColor = lineColoring.getForeColor();
308         else
309             foreColor = defaultColoring.getForeColor();
310         
311         if (lineColoring.getFont() != null) {
312             Font JavaDoc lineFont = lineColoring.getFont();
313             font = (lineFont != null) ? lineFont.deriveFont((float)lineFont.getSize()-1) : null;
314         } else {
315             font = defaultColoring.getFont();
316             font = new Font JavaDoc("Monospaced", Font.PLAIN, font.getSize()-1); //NOI18N
317
}
318
319         lineHeight = editorUI.getLineHeight();
320
321         showLineNumbers = editorUI.lineNumberVisibleSetting;
322
323         drawOverLineNumbers = AnnotationTypes.getTypes().isGlyphsOverLineNumbers().booleanValue();
324         
325         
326         
327         init = true;
328
329         // initialize the value with current number of lines
330
highestLineNumber = getLineCount();
331         
332         repaint();
333         resize();
334     }
335     
336    
337     protected void resize() {
338         Dimension JavaDoc dim = new Dimension JavaDoc();
339         glyphGutterWidth = getWidthDimension();
340         dim.width = glyphGutterWidth;
341         dim.height = getHeightDimension();
342         
343         
344         // enlarge the gutter so that inserting new lines into
345
// document does not cause resizing too often
346
dim.height += ENLARGE_GUTTER_HEIGHT * lineHeight;
347         
348         setPreferredSize(dim);
349
350         revalidate();
351     }
352
353     /** Return number of lines in the document */
354     protected int getLineCount() {
355         int lineCnt;
356         try {
357             if (doc != null) {
358                 lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1;
359             } else { // deactivated
360
lineCnt = 1;
361             }
362         } catch (BadLocationException JavaDoc e) {
363             lineCnt = 1;
364         }
365         return lineCnt;
366     }
367
368     /** Gets number of digits in the number */
369     protected int getDigitCount(int number) {
370         return Integer.toString(number).length();
371     }
372
373     protected int getLineNumberWidth() {
374         int newWidth = 0;
375         
376         if (editorUI != null) {
377             /*
378             Insets insets = editorUI.getLineNumberMargin();
379             if (insets != null) {
380                 newWidth += insets.left + insets.right;
381             }
382              */

383             newWidth += getDigitCount(highestLineNumber) * editorUI.getLineNumberDigitWidth();
384         }
385         
386         return newWidth;
387     }
388     
389     protected int getWidthDimension() {
390         int newWidth = 0;
391         
392         if (showLineNumbers) {
393             int lineNumberWidth = getLineNumberWidth();
394             newWidth = leftGap + lineNumberWidth + rightGap;
395         } else {
396             if (editorUI != null) {
397                 if (annos.isGlyphColumn() ||
398                         AnnotationTypes.getTypes().isShowGlyphGutter().booleanValue()){
399                     newWidth += glyphWidth;
400                 }
401
402                 if (annos.isGlyphButtonColumn()){
403                              newWidth += glyphButtonWidth;
404                 }
405             }
406         }
407
408         return newWidth;
409     }
410     
411     protected int getHeightDimension() {
412         if (editorUI == null)
413             return 0;
414         JComponent JavaDoc comp = editorUI.getComponent();
415         if (comp == null)
416             return 0;
417         return highestLineNumber * lineHeight + (int)comp.getSize().getHeight();
418     }
419     
420
421     void paintGutterForView(Graphics JavaDoc g, View JavaDoc view, int y){
422         if (editorUI == null)
423             return ;
424         Rectangle JavaDoc clip = g.getClipBounds();
425         JTextComponent JavaDoc component = editorUI.getComponent();
426         if (component == null) return;
427         BaseTextUI textUI = (BaseTextUI)component.getUI();
428
429
430         Rectangle JavaDoc rec = new Rectangle JavaDoc(0, y, 0, editorUI.getLineHeight());
431
432 // Should already be filled by back color in paintComponent()
433
// g.setColor(backgroundColor);
434
// Rectangle fillRect = new Rectangle(clip.x, rec.y, getSize().width, rec.height);
435
// g.fillRect(0, rec.y, getSize().width, rec.height);
436

437         g.setFont(font);
438         g.setColor(foreColor);
439
440         FontMetrics JavaDoc fm = FontMetricsCache.getFontMetrics(font, this);
441         Element JavaDoc rootElem = textUI.getRootView(component).getElement();
442         int line = rootElem.getElementIndex(view.getStartOffset());
443         // find the nearest visible line with an annotation
444
int lineWithAnno = annos.getNextLineWithAnnotation(line);
445
446         int lineNumberWidth = fm.stringWidth(String.valueOf(line + 1));
447
448         int count = annos.getNumberOfAnnotations(line);
449         AnnotationDesc anno = annos.getActiveAnnotation(line);
450         
451         if (showLineNumbers){
452             boolean glyphHasIcon = false;
453             if (line == lineWithAnno){
454                 if (anno != null && !(anno.isDefaultGlyph()&&count == 1) && anno.getGlyph()!=null){
455                     glyphHasIcon = true;
456                 }
457             }
458             if ((!glyphHasIcon) ||
459                     (!drawOverLineNumbers) ||
460                     (drawOverLineNumbers && line != lineWithAnno) ) {
461                 g.drawString(String.valueOf(line + 1), glyphGutterWidth-lineNumberWidth-rightGap, y + editorUI.getLineAscent());
462             }
463         }
464
465         // draw anotation if we get to the line with some annotation
466
if (line == lineWithAnno) {
467             int xPos = (showLineNumbers) ? getLineNumberWidth() : 0;
468             if (drawOverLineNumbers) {
469                 xPos = getWidth() - glyphWidth;
470                 if (count > 1)
471                     xPos -= glyphButtonWidth;
472             }
473
474             if (anno != null) {
475                 // draw the glyph only when the annotation type has its own icon (no the default one)
476
// or in case there is more than one annotations on the line
477
if ( ! (count == 1 && anno.isDefaultGlyph()) ) {
478                     if (anno.getGlyph() != null && prepareImage(anno.getGlyph(), imgObserver))
479                         g.drawImage(anno.getGlyph(), xPos, y + (lineHeight-anno.getGlyph().getHeight(null)) / 2 + 1, null);
480                 }
481             }
482
483             // draw cycling button if there is more than one annotations on the line
484
if (count > 1)
485                 if (anno.getGlyph() != null && prepareImage(gutterButton, imgObserver) && prepareImage(anno.getGlyph(), imgObserver))
486                     g.drawImage(gutterButton, xPos+glyphWidth-1, y + (lineHeight-anno.getGlyph().getHeight(null)) / 2, null);
487
488             // update the value with next line with some anntoation
489
lineWithAnno = annos.getNextLineWithAnnotation(line+1);
490         }
491     }
492     
493     /** Paint the gutter itself */
494     public void paintComponent(Graphics JavaDoc g) {
495         super.paintComponent(g);
496         if (editorUI == null)
497             return ;
498
499         // Possibly apply the rendering hints
500
if (renderingHints != null) {
501             ((java.awt.Graphics2D JavaDoc)g).setRenderingHints(renderingHints);
502         }
503         
504         // if the gutter was not initialized yet, skip the painting
505
if (!init) return;
506         
507         Rectangle JavaDoc clip = g.getClipBounds();
508
509         JTextComponent JavaDoc component = editorUI.getComponent();
510         if (component == null) return;
511         
512         BaseTextUI textUI = (BaseTextUI)component.getUI();
513         Element JavaDoc rootElem = textUI.getRootView(component).getElement();
514
515         View JavaDoc rootView = Utilities.getDocumentView(component);
516         if (rootView == null) return;
517       
518         g.setColor(backgroundColor);
519         g.fillRect(clip.x, clip.y, clip.width, clip.height);
520         
521         //painting gutter line
522
g.setColor(SettingsDefaults.defaultGutterLine);
523         g.drawLine(glyphGutterWidth-1, clip.y, glyphGutterWidth-1, clip.height + clip.y);
524
525         AbstractDocument JavaDoc doc = (AbstractDocument JavaDoc)component.getDocument();
526         doc.readLock();
527         try{
528             foldHierarchy.lock();
529             try{
530                 int startPos = textUI.getPosFromY(clip.y);
531                 int startViewIndex = rootView.getViewIndex(startPos,Position.Bias.Forward);
532                 int rootViewCount = rootView.getViewCount();
533
534                 if (startViewIndex >= 0 && startViewIndex < rootViewCount) {
535                     // find the nearest visible line with an annotation
536
int lineWithAnno = annos.getNextLineWithAnnotation(rootElem.getElementIndex(startPos));
537                     Rectangle JavaDoc rec = textUI.modelToView(component, rootView.getView(startViewIndex).getStartOffset());
538                     int y = (rec == null) ? 0 : rec.y;
539
540                     int clipEndY = clip.y + clip.height;
541                     for (int i = startViewIndex; i < rootViewCount; i++){
542                         View JavaDoc view = rootView.getView(i);
543                         paintGutterForView(g, view, y);
544                         y += editorUI.getLineHeight();
545                         if (y >= clipEndY) {
546                             break;
547                         }
548                     }
549                 }
550                 
551             }finally{
552                 foldHierarchy.unlock();
553             }
554         }catch(BadLocationException JavaDoc ble){
555             ErrorManager.getDefault().notify(ble);
556         }finally{
557             doc.readUnlock();
558         }
559     }
560
561     /** Data for the line has changed and the line must be redraw. */
562     public void changedLine(int line) {
563         
564         if (!init || editorUI == null)
565             return;
566
567         // reset cache if there was some change
568
cachedCountOfAnnos = -1;
569         
570         // redraw also lines around - three lines will be redrawn
571
if (line > 0)
572             line--;
573         JTextComponent JavaDoc component = editorUI.getComponent();
574         if (component!=null){
575             BaseTextUI textUI = (BaseTextUI)component.getUI();
576             try{
577                 Element JavaDoc rootElem = component.getDocument().getDefaultRootElement();
578                 if (line >= rootElem.getElementCount()) { // #42504
579
return;
580                 }
581                 Element JavaDoc lineElem = rootElem.getElement(line);
582                 if (lineElem == null) return;
583                 int lineOffset = lineElem.getStartOffset();
584                 Rectangle JavaDoc mtvRect = textUI.modelToView(component, lineOffset);
585                 if (mtvRect == null) return;
586                 repaint(0, mtvRect.y, (int)getSize().getWidth(), 3*lineHeight);
587                 checkSize();
588             }catch(BadLocationException JavaDoc ble){
589                 ErrorManager.getDefault().notify(ble);
590             }
591         }
592     }
593
594     /** Repaint whole gutter.*/
595     public void changedAll() {
596
597         if (!init || editorUI == null)
598             return;
599
600         // reset cache if there was some change
601
cachedCountOfAnnos = -1;
602         
603         int lineCnt;
604         try {
605             lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1;
606         } catch (BadLocationException JavaDoc e) {
607             lineCnt = 1;
608         }
609
610         repaint();
611         checkSize();
612     }
613
614     /** Check whether it is not necessary to resize the gutter */
615     protected void checkSize() {
616         int count = getLineCount();
617         if (count > highestLineNumber) {
618             highestLineNumber = count;
619         }
620         Dimension JavaDoc dim = getPreferredSize();
621         if (getWidthDimension() > dim.width ||
622             getHeightDimension() > dim.height) {
623             resize();
624         }
625         
626     }
627
628     /** Get tooltip text for the mouse position */
629     // TODO: does not work for asynchronous tooltip texts
630
public String JavaDoc getToolTipText (MouseEvent JavaDoc e) {
631         if (editorUI == null)
632             return null;
633         int line = getLineFromMouseEvent(e);
634         if (annos.getNumberOfAnnotations(line) == 0)
635             return null;
636         if (isMouseOverCycleButton(e) && annos.getNumberOfAnnotations(line) > 1) {
637             return java.text.MessageFormat.format (
638                 NbBundle.getBundle(BaseKit.class).getString ("cycling-glyph_tooltip"), //NOI18N
639
new Object JavaDoc[] { new Integer JavaDoc (annos.getNumberOfAnnotations(line)) });
640         }
641         else if (isMouseOverGlyph(e)) {
642             return annos.getActiveAnnotation(line).getShortDescription();
643         }
644         else
645             return null;
646     }
647
648     /** Count the X position of the glyph on the line. */
649     private int getXPosOfGlyph(int line) {
650         if (editorUI == null)
651             return 0;
652         int xPos = (showLineNumbers) ? getLineNumberWidth() : 0;
653         if (drawOverLineNumbers) {
654             xPos = getWidth() - glyphWidth;
655             if (cachedCountOfAnnos == -1 || cachedCountOfAnnosForLine != line) {
656                 cachedCountOfAnnos = annos.getNumberOfAnnotations(line);
657                 cachedCountOfAnnosForLine = line;
658             }
659             if (cachedCountOfAnnos > 1)
660                 xPos -= glyphButtonWidth;
661         }
662         return xPos;
663     }
664
665     /** Check whether the mouse is over some glyph icon or not */
666     private boolean isMouseOverGlyph(MouseEvent JavaDoc e) {
667         int line = getLineFromMouseEvent(e);
668         if (e.getX() >= getXPosOfGlyph(line) && e.getX() <= getXPosOfGlyph(line)+glyphWidth)
669             return true;
670         else
671             return false;
672     }
673     
674     /** Check whether the mouse is over the cycling button or not */
675     private boolean isMouseOverCycleButton(MouseEvent JavaDoc e) {
676         int line = getLineFromMouseEvent(e);
677         if (e.getX() >= getXPosOfGlyph(line)+glyphWidth && e.getX() <= getXPosOfGlyph(line)+glyphWidth+glyphButtonWidth)
678             return true;
679         else
680             return false;
681     }
682
683     public JComponent JavaDoc createSideBar(JTextComponent JavaDoc target) {
684         EditorUI eui = Utilities.getEditorUI(target);
685         if (eui == null){
686             return null;
687         }
688         GlyphGutter glyph = new GlyphGutter(eui);
689         eui.setGlyphGutter(glyph);
690         return glyph;
691     }
692     
693     private int getLineFromMouseEvent(MouseEvent JavaDoc e){
694         int line = -1;
695         if (editorUI != null) {
696             try{
697                 JTextComponent JavaDoc component = editorUI.getComponent();
698                 BaseTextUI textUI = (BaseTextUI)component.getUI();
699                 int clickOffset = textUI.viewToModel(component, new Point JavaDoc(0, e.getY()));
700                 line = Utilities.getLineOffset(doc, clickOffset);
701             }catch (BadLocationException JavaDoc ble){
702                 ble.printStackTrace();
703             }
704         }
705         return line;
706     }
707     
708     class GutterMouseListener extends MouseAdapter JavaDoc implements MouseMotionListener {
709         
710         /** start line of the dragging. */
711         private int dragStartLine;
712         /** end line of the dragging. */
713         private int dragEndLine;
714         /** end line of last selection. */
715         private int currentEndLine;
716         /** If true, the selection goes forwards. */
717         private boolean selectForward;
718
719         public void mouseClicked(MouseEvent JavaDoc e) {
720             if (editorUI==null)
721                 return;
722             // cycling button was clicked by left mouse button
723
if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
724                 if (isMouseOverCycleButton(e)) {
725                     int line = getLineFromMouseEvent(e);
726                     annos.activateNextAnnotation(line);
727                 } else {
728                     Action JavaDoc actions[] = ImplementationProvider.getDefault().getGlyphGutterActions(editorUI.getComponent());
729                     if (actions != null && actions.length >0) {
730                         Action JavaDoc a = actions[0]; //TODO - create GUI chooser
731
if (a!=null && a.isEnabled()){
732                             int currentLine = -1;
733                             int line = getLineFromMouseEvent(e);
734                             if (line == -1) return;
735                             try {
736                                 currentLine = Utilities.getLineOffset(doc, editorUI.getComponent().getCaret().getDot());
737                             } catch (BadLocationException JavaDoc ex) {
738                                 return;
739                             }
740                             if (line != currentLine) {
741                                 int offset = Utilities.getRowStartFromLineOffset(doc, line);
742                                 JumpList.checkAddEntry();
743                                 editorUI.getComponent().getCaret().setDot(offset);
744                             }
745                             a.actionPerformed(new ActionEvent(editorUI.getComponent(), 0, ""));
746                             repaint();
747                         }
748                     } else {
749                         Toolkit.getDefaultToolkit().beep();
750                     }
751                 }
752             }
753         }
754
755         private void showPopup(MouseEvent JavaDoc e) {
756             if (editorUI == null)
757                 return;
758             // annotation glyph was clicked by right mouse button
759
if (e.isPopupTrigger()) {
760                 int line = getLineFromMouseEvent(e);
761                 int offset;
762                 if (annos.getActiveAnnotation(line) != null)
763                     offset = annos.getActiveAnnotation(line).getOffset();
764                 else
765                     offset = Utilities.getRowStartFromLineOffset(doc, line);
766                 if (editorUI.getComponent().getCaret().getDot() != offset)
767                     JumpList.checkAddEntry();
768                 editorUI.getComponent().getCaret().setDot(offset);
769                 JPopupMenu JavaDoc pm = annos.createPopupMenu(Utilities.getKit(editorUI.getComponent()), line);
770                 if (pm != null) {
771                     pm.show(GlyphGutter.this, e.getX(), e.getY());
772                 }
773                 pm.addPopupMenuListener( new PopupMenuListener JavaDoc() {
774                         public void popupMenuCanceled(PopupMenuEvent JavaDoc e2) {
775                             editorUI.getComponent().requestFocus();
776                         }
777                         public void popupMenuWillBecomeInvisible(PopupMenuEvent JavaDoc e2) {
778                             editorUI.getComponent().requestFocus();
779                         }
780                         public void popupMenuWillBecomeVisible(PopupMenuEvent JavaDoc e2) {
781                         }
782                     });
783             }
784         }
785         
786         public void mouseReleased(MouseEvent JavaDoc e) {
787             showPopup(e);
788         }
789         
790         public void mousePressed (MouseEvent JavaDoc e) {
791             showPopup(e);
792             // "click gutter selects line" functionality was disabled
793
// // only react when it is not a cycling button
794
// if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
795
// if (! isMouseOverCycleButton(e)) {
796
// dragStartLine = (int)( (float)e.getY() / (float)lineHeight );
797
// updateSelection (true);
798
// }
799
// }
800
}
801         
802         public void mouseDragged(MouseEvent JavaDoc e) {
803             // "click gutter selects line" functionality was disabled
804
// dragEndLine = (int)( (float)e.getY() / (float)lineHeight );
805
// updateSelection (false);
806
}
807         
808         public void mouseMoved(MouseEvent JavaDoc e) {}
809         
810         /** Updates the selection */
811         private void updateSelection (boolean newSelection) {
812             if (editorUI == null)
813                 return ;
814             javax.swing.text.JTextComponent JavaDoc comp = Utilities.getLastActiveComponent ();
815             try {
816                 if (newSelection) {
817                     selectForward = true;
818                     // try to get the startOffset. In case of -1 it is most
819
// likely the end of the document
820
int rowStart = Utilities.getRowStartFromLineOffset (doc, dragStartLine);
821                     if (rowStart < 0) {
822                         rowStart = Utilities.getRowStart (doc, doc.getLength ());
823                         dragStartLine = Utilities.getLineOffset (doc, rowStart);
824                     }
825                     comp.setCaretPosition (rowStart);
826                     int offSet = Utilities.getRowEnd (doc, rowStart);
827                     if (offSet < doc.getLength()) {
828                         offSet = offSet + 1;
829                     }
830                     comp.moveCaretPosition (offSet);
831                     currentEndLine = dragEndLine = dragStartLine;
832                 } else {
833                     if (currentEndLine == dragEndLine) return;
834                     // select backwards
835
if (dragEndLine < dragStartLine) {
836                         if (selectForward) {
837                             // selection start should be at start of (dragLine + 1)
838
int offSet = Utilities.getRowStartFromLineOffset (doc, dragStartLine + 1);
839                             if (offSet < 0) {
840                                 offSet = Utilities.getRowEnd (doc, Utilities.getRowStartFromLineOffset (doc, dragStartLine));
841                             }
842                             comp.setCaretPosition (offSet);
843                             selectForward = false;
844                         }
845                         int rowStart = Utilities.getRowStartFromLineOffset (doc, dragEndLine);
846                         if (rowStart < 0) rowStart = 0;
847                         comp.moveCaretPosition (rowStart);
848                     }
849                     // select forwards
850
else {
851                         if (! selectForward) {
852                             // select start should be at dragStartLine
853
comp.setCaretPosition (Utilities.getRowStartFromLineOffset (doc, dragStartLine));
854                             selectForward = true;
855                         }
856                         // try to get the begin of (endLine + 1)
857
int offSet = Utilities.getRowStartFromLineOffset (doc, dragEndLine + 1);;
858                         // for last line or more -1 is returned, so set to docLength...
859
if (offSet < 0) {
860                             offSet = doc.getLength ();
861                         }
862                         comp.moveCaretPosition (offSet);
863                     }
864                 }
865                 currentEndLine = dragEndLine;
866             } catch (BadLocationException JavaDoc ble) {
867                 ErrorManager.getDefault().notify(ble);
868             }
869         }
870     }
871
872     class GlyphGutterFoldHierarchyListener implements FoldHierarchyListener{
873     
874         public GlyphGutterFoldHierarchyListener(){
875         }
876         
877         public void foldHierarchyChanged(FoldHierarchyEvent evt) {
878             repaint();
879         }
880     }
881     
882     /** Listening to EditorUI to properly deinstall attached listeners */
883     class EditorUIListener implements PropertyChangeListener JavaDoc{
884         public void propertyChange (PropertyChangeEvent JavaDoc evt) {
885             if (evt!=null && EditorUI.COMPONENT_PROPERTY.equals(evt.getPropertyName())) {
886                 if (evt.getNewValue() == null){
887                     // component deinstalled, lets uninstall all isteners
888
editorUI.removePropertyChangeListener(editorUIListener);
889                     annos.removeAnnotationsListener(GlyphGutter.this);
890                     foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
891                     if (gutterMouseListener!=null){
892                         removeMouseListener(gutterMouseListener);
893                         removeMouseMotionListener(gutterMouseListener);
894                     }
895                     if (annoTypesListener !=null){
896                         AnnotationTypes.getTypes().removePropertyChangeListener(annoTypesListener);
897                     }
898                     foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
899                     foldHierarchy = null;
900                     // Release document reference
901
doc = null;
902                     editorUI.removePropertyChangeListener(this);
903                     editorUI = null;
904                     annos = null;
905                 }
906             }
907         }
908     }
909
910     private static class Observer implements ImageObserver JavaDoc {
911
912         WeakReference JavaDoc glyphGutter;
913
914         private Observer(GlyphGutter gutter) {
915             glyphGutter = new WeakReference JavaDoc(gutter);
916         }
917
918         public boolean imageUpdate(Image JavaDoc img, int infoflags, int x, int y, int width, int height) {
919             if ((infoflags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) {
920                 GlyphGutter obj = (GlyphGutter)glyphGutter.get();
921                 if (obj != null)
922                     obj.repaint();
923             }
924             return true;
925         }
926     }
927
928 }
929
Popular Tags