Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jsource/gui/Gutter.java


1   package jsource.gui;
2   
3   
4   /**
5    * @(#)Gutter.java  12/16/02
6    *
7    * This program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU Library General Public License as published
9    * by the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU Library General Public License for more details.
16   */
17  import java.awt.*;
18  import java.awt.event.*;
19  import javax.swing.*;
20  
21  
22  /**
23   * <code>Gutter</code> is the component that displays line numbers to the left of the text area.
24   * Based on jEdit's Gutter component by Mike Dillon and Slava Pestov.
25   *
26   * @author Panagiotis Plevrakis
27   * <br>Email: pplevrakis@hotmail.com
28   * <br>URL:   http://jsource.sourceforge.net
29   */
30  public class Gutter extends JLabel implements SwingConstants {
31  
32      /** Internal <code>Gutter</code> color constants */
33      final static Color DEFAULT_BG_COLOUR = new Color(133, 133, 133);
34      final static Color DEFAULT_FG_COLOUR = new Color(209, 209, 209);
35      final static Color DEFAULT_DIVIDER_COLOUR = Color.red;
36      final static Color DEFAULT_CURRENT_LINE_COLOUR = Color.blue;
37      final static Color DEFAULT_INTERVAL_COLOUR = Color.yellow;
38  
39      /** A reference to the <code>JSEditor</code> object where the <code>Gutter</code> is attached */
40      private JSEditor editor;
41  
42      private JPopupMenu context;
43  
44      private int baseline = 0;
45      private int ileft = 0;
46  
47      private int mMinWidth;
48  
49      private Dimension gutterSize;
50      private Dimension collapsedSize;
51  
52      private Color intervalHighlight;
53      private Color currentLineHighlight;
54      private Color dividerColour;
55  
56      private FontMetrics fm;
57  
58      private int alignment;
59  
60      private int interval = 5;
61      private boolean lineNumberingEnabled = true;
62      private boolean currentLineHighlightEnabled = false;
63      private boolean collapsed = false;
64  
65      public Gutter(JSEditor editor) {
66          this.editor = editor;
67  
68          // minimum width of gutter - enough to display 5 digits
69          mMinWidth = editor.getPainter().getFontMetrics().charWidth('8') * 5;
70          // minimum size of gutter when expanded
71          gutterSize = new Dimension(mMinWidth, 100);
72          // minimum size of gutter when collapsed
73          collapsedSize = new Dimension(10, 100);
74  
75          setDoubleBuffered(true);
76  
77          MouseHandler ml = new MouseHandler();
78  
79          addMouseListener(ml);
80  
81          setBackground(DEFAULT_BG_COLOUR);
82          setForeground(DEFAULT_FG_COLOUR);
83          setDividerColour(DEFAULT_DIVIDER_COLOUR);
84          setHighlightedForeground(DEFAULT_INTERVAL_COLOUR);
85          setCurrentLineForeground(DEFAULT_CURRENT_LINE_COLOUR);
86      }
87  
88      public void paintComponent(Graphics gfx) {
89          if (!collapsed) {
90              // fill the background
91              Rectangle r = gfx.getClipBounds();
92  
93              gfx.setColor(getBackground());
94              gfx.fillRect(r.x, r.y, r.width, r.height);
95  
96              // draw the divider on RHS of gutter
97              Color save = gfx.getColor();
98  
99              gfx.setColor(dividerColour);
100             gfx.drawLine(gutterSize.width - 3, r.y, gutterSize.width - 3, r.y + r.height);
101             gfx.setColor(save);
102 
103             // paint line numbers, if they are enabled
104             if (lineNumberingEnabled) paintLineNumbers(gfx);
105         }
106     }
107 
108     public void paint(Graphics gfx) {
109         paintComponent(gfx);
110     }
111 
112     private void paintLineNumbers(Graphics gfx) {
113         FontMetrics pfm = editor.getPainter().getFontMetrics();
114         int lineHeight = pfm.getHeight();
115 
116         Rectangle clip = gfx.getClipBounds();
117 
118         int baseline = editor.lineToY(editor.getFirstLine());
119 
120         int firstLine = editor.getFirstLine();
121         int lastLine = firstLine + 1 + clip.height / lineHeight;
122         int caretLine = editor.getCaretLine() + 1;
123 
124         int firstValidLine = firstLine > 1 ? firstLine : 1;
125         int lastValidLine = (lastLine > editor.getLineCount())
126                 ? editor.getLineCount()
127                 : lastLine;
128 
129         boolean highlightCurrentLine = currentLineHighlightEnabled
130                 && (editor.getSelectionStart() == editor.getSelectionEnd());
131 
132         gfx.setFont(editor.getPainter().getFont());
133 
134         Color fg = getForeground();
135         Color hfg = getHighlightedForeground();
136         Color clfg = getCurrentLineForeground();
137 
138         String number;
139         int offset;
140 
141         for (int line = firstLine; line <= lastLine;
142                 line++, baseline += lineHeight) {
143             // only print numbers for valid lines
144             if (line < firstValidLine || line > lastValidLine)
145                 continue;
146 
147             number = Integer.toString(line);
148 
149             // if breakpoint at line, highlight in red
150             if (editor.isBreakpoint(line)) {
151                 Color save = gfx.getColor();
152 
153                 gfx.setColor(Color.red);
154                 gfx.fillRect(0, baseline - lineHeight, gutterSize.width - 3, lineHeight);
155                 gfx.setColor(save);
156             }
157 
158             switch (alignment) {
159             case RIGHT:
160                 offset = gutterSize.width - collapsedSize.width
161                         - (fm.stringWidth(number) + 1);
162                 break;
163 
164             case CENTER:
165                 offset = ((gutterSize.width - collapsedSize.width)
166                         - fm.stringWidth(number))
167                         / 2;
168                 break;
169 
170             case LEFT:
171             default:
172                 offset = 1;
173             }
174 
175             if (line == caretLine && highlightCurrentLine) {
176                 gfx.setColor(clfg);
177             } else if (interval > 1 && line % interval == 0) {
178                 gfx.setColor(hfg);
179             } else {
180                 gfx.setColor(fg);
181             }
182 
183             gfx.drawString(number, ileft + offset, baseline);
184         }
185     }
186 
187     /**
188      * Marks a line as needing a repaint.
189      * @param line The line to invalidate
190      */
191     public final void invalidateLine(int line) {
192         FontMetrics pfm = editor.getPainter().getFontMetrics();
193 
194         repaint(0, editor.lineToY(line) + pfm.getDescent() + pfm.getLeading(),
195                 getWidth(), pfm.getHeight());
196     }
197 
198     /**
199      * Marks a range of lines as needing a repaint.
200      * @param firstLine The first line to invalidate
201      * @param lastLine The last line to invalidate
202      */
203     public final void invalidateLineRange(int firstLine, int lastLine) {
204         FontMetrics pfm = editor.getPainter().getFontMetrics();
205 
206         repaint(0, editor.lineToY(firstLine) + pfm.getDescent() + pfm.getLeading(),
207                 getWidth(), (lastLine - firstLine + 1) * pfm.getHeight());
208     }
209 
210     /*
211      * <code>JComponent.setFont(Font)</code> is overridden here to cache the baseline for
212      * the font. This avoids having to get the font metrics during every repaint.
213      */
214     public void setFont(Font font) {
215         super.setFont(font);
216 
217         fm = getFontMetrics(font);
218         baseline = fm.getAscent();
219     }
220 
221     /**
222      * Get the foreground color for highlighted line numbers
223      * @return The highlight color
224      */
225     public Color getHighlightedForeground() {
226         return intervalHighlight;
227     }
228 
229     public void setHighlightedForeground(Color highlight) {
230         intervalHighlight = highlight;
231     }
232 
233     public Color getCurrentLineForeground() {
234         return currentLineHighlight;
235     }
236 
237     public void setCurrentLineForeground(Color highlight) {
238         currentLineHighlight = highlight;
239     }
240 
241     /**
242      * Set the width of the expanded gutter
243      * @param width The gutter width
244      */
245     public void setGutterWidth(int width) {
246         if (width < collapsedSize.width) width = collapsedSize.width;
247 
248         gutterSize.width = width;
249 
250         // if the gutter is expanded, ask the text area to revalidate
251         // the layout to resize the gutter
252         if (!collapsed) editor.revalidate();
253     }
254 
255     /**
256      * Get the width of the expanded gutter
257      * @return The gutter width
258      */
259     public int getGutterWidth() {
260         return gutterSize.width;
261     }
262 
263     /*
264      * Component.getPreferredSize() is overridden here to support the
265      * collapsing behavior.
266      */
267     public Dimension getPreferredSize() {
268         if (collapsed) {
269             return collapsedSize;
270         } else {
271             return gutterSize;
272         }
273     }
274 
275     public Dimension getMinimumSize() {
276         return getPreferredSize();
277     }
278 
279     /**
280      * Identifies whether or not the line numbers are drawn in the gutter
281      * @return true if the line numbers are drawn, false otherwise
282      */
283     public boolean isLineNumberingEnabled() {
284         return lineNumberingEnabled;
285     }
286 
287     /**
288      * Turns the line numbering on or off and causes the gutter to be
289      * repainted.
290      * @param enabled true if line numbers are drawn, false otherwise
291      */
292     public void setLineNumberingEnabled(boolean enabled) {
293         if (lineNumberingEnabled == enabled) return;
294 
295         lineNumberingEnabled = enabled;
296 
297         repaint();
298     }
299 
300     /**
301      * Identifies whether the horizontal alignment of the line numbers.
302      * @return Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
303      */
304     public int getLineNumberAlignment() {
305         return alignment;
306     }
307 
308     /**
309      * Sets the horizontal alignment of the line numbers.
310      * @param alignment Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
311      */
312     public void setLineNumberAlignment(int alignment) {
313         if (this.alignment == alignment) return;
314 
315         this.alignment = alignment;
316 
317         repaint();
318     }
319 
320     /**
321      * Identifies whether the gutter is collapsed or expanded.
322      * @return true if the gutter is collapsed, false if it is expanded
323      */
324     public boolean isCollapsed() {
325         return collapsed;
326     }
327 
328     /**
329      * Sets whether the gutter is collapsed or expanded and force the text
330      * area to update its layout if there is a change.
331      * @param collapsed true if the gutter is collapsed,
332      *                   false if it is expanded
333      */
334     public void setCollapsed(boolean collapsed) {
335         if (this.collapsed == collapsed) return;
336 
337         this.collapsed = collapsed;
338 
339         editor.revalidate();
340     }
341 
342     /**
343      * Toggles whether the gutter is collapsed or expanded.
344      */
345     public void toggleCollapsed() {
346         setCollapsed(!collapsed);
347     }
348 
349     /**
350      * Sets the number of lines between highlighted line numbers.
351      * @return The number of lines between highlighted line numbers or
352      *          zero if highlighting is disabled
353      */
354     public int getHighlightInterval() {
355         return interval;
356     }
357 
358     /**
359      * Sets the number of lines between highlighted line numbers. Any value
360      * less than or equal to one will result in highlighting being disabled.
361      * @param interval The number of lines between highlighted line numbers
362      */
363     public void setHighlightInterval(int interval) {
364         if (interval <= 1) interval = 0;
365         this.interval = interval;
366         repaint();
367     }
368 
369     public void setDividerColour(Color c) {
370         dividerColour = c;
371         repaint();
372     }
373 
374     public Color getDividerColour() {
375         return dividerColour;
376     }
377 
378     public boolean isCurrentLineHighlightEnabled() {
379         return currentLineHighlightEnabled;
380     }
381 
382     public void setCurrentLineHighlightEnabled(boolean enabled) {
383         if (currentLineHighlightEnabled == enabled) return;
384 
385         currentLineHighlightEnabled = enabled;
386 
387         repaint();
388     }
389 
390     public JPopupMenu getContextMenu() {
391         return context;
392     }
393 
394     public void setContextMenu(JPopupMenu context) {
395         this.context = context;
396     }
397 
398     class MouseHandler extends MouseAdapter {
399         public void mouseClicked(MouseEvent e) {
400             if (e.getClickCount() == 2) toggleCollapsed();
401         }
402     }
403 }