/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/textarea/Gutter.java
Java | 760 lines | 492 code | 97 blank | 171 comment | 76 complexity | 3d32641582214ef0c9bf29ea023a8407 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
1/*
2 * Gutter.java
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1999, 2000 mike dillon
7 * Portions copyright (C) 2001 Slava Pestov
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 */
23
24package org.gjt.sp.jedit.textarea;
25
26//{{{ Imports
27import java.awt.*;
28import java.awt.event.*;
29import java.lang.reflect.Method;
30import java.util.Vector;
31import javax.swing.*;
32import javax.swing.border.*;
33import javax.swing.event.*;
34import org.gjt.sp.jedit.*;
35import org.gjt.sp.util.Log;
36//}}}
37
38public class Gutter extends JComponent implements SwingConstants
39{
40 //{{{ Gutter constructor
41 public Gutter(View view, JEditTextArea textArea)
42 {
43 this.view = view;
44 this.textArea = textArea;
45
46 highlights = new Vector();
47
48 setDoubleBuffered(true);
49
50 MouseHandler ml = new MouseHandler();
51 addMouseListener(ml);
52 addMouseMotionListener(ml);
53 } //}}}
54
55 //{{{ paintComponent() method
56 public void paintComponent(Graphics gfx)
57 {
58 // fill the background
59 Rectangle clip = gfx.getClipBounds();
60 gfx.setColor(getBackground());
61 gfx.fillRect(clip.x, clip.y, clip.width, clip.height);
62
63 // if buffer is loading, don't paint anything
64 if (!textArea.getBuffer().isLoaded())
65 return;
66
67 // paint highlights and line numbers
68 int lineHeight = textArea.getPainter().getFontMetrics()
69 .getHeight();
70
71 int firstLine = clip.y / lineHeight + textArea.getFirstLine();
72 int lastLine = (clip.y + clip.height - 1) / lineHeight
73 + textArea.getFirstLine();
74
75 FontMetrics pfm = textArea.getPainter().getFontMetrics();
76 Color fg = getForeground();
77
78 int baseline = (int)((this.baseline + lineHeight
79 - pfm.getDescent()) / 2.0);
80
81 boolean highlightCurrentLine = currentLineHighlightEnabled
82 && textArea.selection.size() == 0;
83
84 int y = (clip.y - clip.y % lineHeight);
85
86 Buffer buffer = textArea.getBuffer();
87
88 FoldVisibilityManager foldVisibilityManager
89 = textArea.getFoldVisibilityManager();
90
91 int lastValidLine = (lastLine >= foldVisibilityManager.getVirtualLineCount())
92 ? foldVisibilityManager.getVirtualLineCount() - 1 : lastLine;
93
94 for (int line = firstLine; line <= lastLine;
95 line++, y += lineHeight)
96 {
97 boolean valid = (line >= firstLine && line <= lastValidLine);
98
99 //{{{ Paint plugin highlights
100 if(highlights.size() != 0)
101 {
102 for(int i = 0; i < highlights.size(); i++)
103 {
104 TextAreaHighlight highlight = (TextAreaHighlight)
105 highlights.elementAt(i);
106 try
107 {
108 highlight.paintHighlight(gfx,line,
109 y - fm.getLeading() - fm.getDescent());
110 }
111 catch(Throwable t)
112 {
113 Log.log(Log.ERROR,this,t);
114
115 // remove it so editor can continue
116 // functioning
117 highlights.removeElementAt(i);
118 i--;
119 }
120 }
121 } //}}}
122
123 if(!valid)
124 return;
125
126 int physicalLine = foldVisibilityManager
127 .virtualToPhysical(line);
128
129 //{{{ Paint fold triangles
130 if(physicalLine != buffer.getLineCount() - 1
131 && buffer.isFoldStart(physicalLine))
132 {
133 int _y = y + lineHeight / 2;
134 gfx.setColor(foldColor);
135 if(foldVisibilityManager.isLineVisible(physicalLine + 1))
136 {
137 gfx.drawLine(1,_y - 3,10,_y - 3);
138 gfx.drawLine(2,_y - 2,9,_y - 2);
139 gfx.drawLine(3,_y - 1,8,_y - 1);
140 gfx.drawLine(4,_y,7,_y);
141 gfx.drawLine(5,_y + 1,6,_y + 1);
142 }
143 else
144 {
145 gfx.drawLine(4,_y - 5,4,_y + 4);
146 gfx.drawLine(5,_y - 4,5,_y + 3);
147 gfx.drawLine(6,_y - 3,6,_y + 2);
148 gfx.drawLine(7,_y - 2,7,_y + 1);
149 gfx.drawLine(8,_y - 1,8,_y);
150 }
151 } //}}}
152 //{{{ Paint bracket scope
153 else if(bracketHighlight)
154 {
155 if(textArea.isBracketHighlightVisible())
156 {
157 int bracketLine = textArea.getBracketLine();
158 int caretLine = textArea.getCaretLine();
159 if(caretLine != bracketLine)
160 {
161 if(caretLine > bracketLine)
162 {
163 int tmp = caretLine;
164 caretLine = bracketLine;
165 bracketLine = tmp;
166 }
167
168 gfx.setColor(bracketHighlightColor);
169 if(physicalLine == caretLine)
170 {
171 gfx.fillRect(5,
172 y
173 + lineHeight / 2,
174 5,
175 2);
176 gfx.fillRect(5,
177 y
178 + lineHeight / 2,
179 2,
180 lineHeight - lineHeight / 2);
181 }
182 else if(physicalLine == bracketLine)
183 {
184 gfx.fillRect(5,
185 y,
186 2,
187 lineHeight / 2);
188 gfx.fillRect(5,
189 y + lineHeight / 2,
190 5,
191 2);
192 }
193 else if(physicalLine > caretLine
194 && physicalLine < bracketLine)
195 {
196 gfx.fillRect(5,
197 y,
198 2,
199 lineHeight);
200 }
201 }
202 }
203 } //}}}
204
205 //{{{ Paint line numbers
206 if(expanded)
207 {
208 String number = Integer.toString(physicalLine + 1);
209
210 int offset;
211 switch (alignment)
212 {
213 case RIGHT:
214 offset = gutterSize.width - collapsedSize.width
215 - (fm.stringWidth(number) + 1);
216 break;
217 case CENTER:
218 offset = ((gutterSize.width - collapsedSize.width)
219 - fm.stringWidth(number)) / 2;
220 break;
221 case LEFT: default:
222 offset = 0;
223 break;
224 }
225
226 if (physicalLine == textArea.getCaretLine() && highlightCurrentLine)
227 {
228 gfx.setColor(currentLineHighlight);
229 }
230 else if (interval > 1 && (line + 1) % interval == 0)
231 gfx.setColor(intervalHighlight);
232 else
233 gfx.setColor(fg);
234
235 gfx.drawString(number, FOLD_MARKER_SIZE + offset,
236 baseline + y);
237 } //}}}
238 }
239 } //}}}
240
241 //{{{ addCustomHighlight() method
242 /**
243 * Adds a custom highlight painter.
244 * @param highlight The highlight
245 */
246 public void addCustomHighlight(TextAreaHighlight highlight)
247 {
248 highlights.addElement(highlight);
249
250 // handle old highlighters
251 Class clazz = highlight.getClass();
252 try
253 {
254 Method method = clazz.getMethod("init",
255 new Class[] { JEditTextArea.class,
256 TextAreaHighlight.class });
257 if(method != null)
258 {
259 Log.log(Log.WARNING,this,clazz.getName()
260 + " uses old highlighter API");
261 method.invoke(highlight,new Object[] { textArea, null });
262 }
263 }
264 catch(Exception e)
265 {
266 // ignore
267 }
268
269 repaint();
270 } //}}}
271
272 //{{{ removeCustomHighlight() method
273 /**
274 * Removes a custom highlight painter.
275 * @param highlight The highlight
276 * @since jEdit 4.0pre1
277 */
278 public void removeCustomHighlight(TextAreaHighlight highlight)
279 {
280 highlights.removeElement(highlight);
281 repaint();
282 } //}}}
283
284 //{{{ setBorder() method
285 /**
286 * Convenience method for setting a default matte border on the right
287 * with the specified border width and color
288 * @param width The border width (in pixels)
289 * @param color1 The focused border color
290 * @param color2 The unfocused border color
291 * @param color3 The gutter/text area gap color
292 */
293 public void setBorder(int width, Color color1, Color color2, Color color3)
294 {
295 this.borderWidth = width;
296
297 focusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
298 new MatteBorder(0,0,0,width,color1));
299 noFocusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
300 new MatteBorder(0,0,0,width,color2));
301 updateBorder();
302 } //}}}
303
304 //{{{ updateBorder() method
305 /**
306 * Sets the border differently if the text area has focus or not.
307 */
308 public void updateBorder()
309 {
310 // because we are called from the text area's focus handler,
311 // we do an invokeLater() so that the view's focus handler
312 // has a chance to execute and set the edit pane properly
313 SwingUtilities.invokeLater(new Runnable()
314 {
315 public void run()
316 {
317 if(view.getEditPane() == null)
318 return;
319
320 if(view.getEditPane().getTextArea() == textArea)
321 setBorder(focusBorder);
322 else
323 setBorder(noFocusBorder);
324 }
325 });
326 } //}}}
327
328 //{{{ setBorder() method
329 /*
330 * JComponent.setBorder(Border) is overridden here to cache the left
331 * inset of the border (if any) to avoid having to fetch it during every
332 * repaint.
333 */
334 public void setBorder(Border border)
335 {
336 super.setBorder(border);
337
338 if (border == null)
339 {
340 collapsedSize.width = 0;
341 collapsedSize.height = 0;
342 }
343 else
344 {
345 Insets insets = border.getBorderInsets(this);
346 collapsedSize.width = FOLD_MARKER_SIZE + insets.right;
347 collapsedSize.height = gutterSize.height
348 = insets.top + insets.bottom;
349 gutterSize.width = FOLD_MARKER_SIZE + insets.right
350 + fm.stringWidth("12345");
351 }
352
353 revalidate();
354 } //}}}
355
356 //{{{ setFont() method
357 /*
358 * JComponent.setFont(Font) is overridden here to cache the baseline for
359 * the font. This avoids having to get the font metrics during every
360 * repaint.
361 */
362 public void setFont(Font font)
363 {
364 super.setFont(font);
365
366 fm = getFontMetrics(font);
367
368 baseline = fm.getAscent();
369
370 Border border = getBorder();
371 if(border != null)
372 {
373 gutterSize.width = FOLD_MARKER_SIZE
374 + border.getBorderInsets(this).right
375 + fm.stringWidth("12345");
376 revalidate();
377 }
378 } //}}}
379
380 //{{{ Getters and setters
381
382 //{{{ getHighlightedForeground() method
383 /**
384 * Get the foreground color for highlighted line numbers
385 * @return The highlight color
386 */
387 public Color getHighlightedForeground()
388 {
389 return intervalHighlight;
390 } //}}}
391
392 //{{{ setHighlightedForeground() method
393 public void setHighlightedForeground(Color highlight)
394 {
395 intervalHighlight = highlight;
396 } //}}}
397
398 //{{{ getCurrentLineForeground() method
399 public Color getCurrentLineForeground()
400 {
401 return currentLineHighlight;
402 } //}}}
403
404 //{{{ setCurrentLineForeground() method
405 public void setCurrentLineForeground(Color highlight)
406 {
407 currentLineHighlight = highlight;
408 } //}}}
409
410 //{{{ getFoldColor() method
411 public Color getFoldColor()
412 {
413 return foldColor;
414 } //}}}
415
416 //{{{ setFoldColor() method
417 public void setFoldColor(Color foldColor)
418 {
419 this.foldColor = foldColor;
420 } //}}}
421
422 //{{{ getPreferredSize() method
423 /*
424 * Component.getPreferredSize() is overridden here to support the
425 * collapsing behavior.
426 */
427 public Dimension getPreferredSize()
428 {
429 if (expanded)
430 return gutterSize;
431 else
432 return collapsedSize;
433 } //}}}
434
435 //{{{ getMinimumSize() method
436 public Dimension getMinimumSize()
437 {
438 return getPreferredSize();
439 } //}}}
440
441 //{{{ getToolTipText() method
442 public String getToolTipText(MouseEvent evt)
443 {
444 for(int i = 0; i < highlights.size(); i++)
445 {
446 TextAreaHighlight highlight =
447 (TextAreaHighlight)
448 highlights.elementAt(i);
449 String toolTip = highlight.getToolTipText(evt);
450 if(toolTip != null)
451 return toolTip;
452 }
453
454 return null;
455 } //}}}
456
457 //{{{ getLineNumberAlignment() method
458 /**
459 * Identifies whether the horizontal alignment of the line numbers.
460 * @return Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
461 */
462 public int getLineNumberAlignment()
463 {
464 return alignment;
465 } //}}}
466
467 //{{{ setLineNumberAlignment() method
468 /**
469 * Sets the horizontal alignment of the line numbers.
470 * @param alignment Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
471 */
472 public void setLineNumberAlignment(int alignment)
473 {
474 if (this.alignment == alignment) return;
475
476 this.alignment = alignment;
477
478 repaint();
479 } //}}}
480
481 //{{{ isExpanded() method
482 /**
483 * Identifies whether the gutter is collapsed or expanded.
484 * @return true if the gutter is expanded, false if it is collapsed
485 */
486 public boolean isExpanded()
487 {
488 return expanded;
489 } //}}}
490
491 //{{{ setExpanded() method
492 /**
493 * Sets whether the gutter is collapsed or expanded and force the text
494 * area to update its layout if there is a change.
495 * @param collapsed true if the gutter is expanded,
496 * false if it is collapsed
497 */
498 public void setExpanded(boolean expanded)
499 {
500 if (this.expanded == expanded) return;
501
502 this.expanded = expanded;
503
504 textArea.revalidate();
505 } //}}}
506
507 //{{{ toggleExpanded() method
508 /**
509 * Toggles whether the gutter is collapsed or expanded.
510 */
511 public void toggleExpanded()
512 {
513 setExpanded(!expanded);
514 } //}}}
515
516 //{{{ getHighlightInterval() method
517 /**
518 * Sets the number of lines between highlighted line numbers.
519 * @return The number of lines between highlighted line numbers or
520 * zero if highlighting is disabled
521 */
522 public int getHighlightInterval()
523 {
524 return interval;
525 } //}}}
526
527 //{{{ setHighlightInterval() method
528 /**
529 * Sets the number of lines between highlighted line numbers. Any value
530 * less than or equal to one will result in highlighting being disabled.
531 * @param interval The number of lines between highlighted line numbers
532 */
533 public void setHighlightInterval(int interval)
534 {
535 if (interval <= 1) interval = 0;
536 this.interval = interval;
537 repaint();
538 } //}}}
539
540 //{{{ isCurrentLineHighlightEnabled() method
541 public boolean isCurrentLineHighlightEnabled()
542 {
543 return currentLineHighlightEnabled;
544 } //}}}
545
546 //{{{ setCurrentLineHighlightEnabled() method
547 public void setCurrentLineHighlightEnabled(boolean enabled)
548 {
549 if (currentLineHighlightEnabled == enabled) return;
550
551 currentLineHighlightEnabled = enabled;
552
553 repaint();
554 } //}}}
555
556 //{{{ getBracketHighlightColor() method
557 /**
558 * Returns the bracket highlight color.
559 */
560 public final Color getBracketHighlightColor()
561 {
562 return bracketHighlightColor;
563 } //}}}
564
565 //{{{ setBracketHighlightColor() method
566 /**
567 * Sets the bracket highlight color.
568 * @param bracketHighlightColor The bracket highlight color
569 * @since jEdit 4.0pre1
570 */
571 public final void setBracketHighlightColor(Color bracketHighlightColor)
572 {
573 this.bracketHighlightColor = bracketHighlightColor;
574 repaint();
575 } //}}}
576
577 //{{{ isBracketHighlightEnabled() method
578 /**
579 * Returns true if bracket highlighting is enabled, false otherwise.
580 * When bracket highlighting is enabled, the bracket matching the
581 * one before the caret (if any) is highlighted.
582 * @since jEdit 4.0pre1
583 */
584 public final boolean isBracketHighlightEnabled()
585 {
586 return bracketHighlight;
587 } //}}}
588
589 //{{{ setBracketHighlightEnabled() method
590 /**
591 * Enables or disables bracket highlighting.
592 * When bracket highlighting is enabled, the bracket matching the
593 * one before the caret (if any) is highlighted.
594 * @param bracketHighlight True if bracket highlighting should be
595 * enabled, false otherwise
596 * @since jEdit 4.0pre1
597 */
598 public final void setBracketHighlightEnabled(boolean bracketHighlight)
599 {
600 this.bracketHighlight = bracketHighlight;
601 repaint();
602 } //}}}
603
604 //}}}
605
606 //{{{ Private members
607 private static final int FOLD_MARKER_SIZE = 12;
608
609 private View view;
610 private JEditTextArea textArea;
611
612 private Vector highlights;
613
614 private int baseline;
615
616 private Dimension gutterSize = new Dimension(0,0);
617 private Dimension collapsedSize = new Dimension(0,0);
618
619 private Color intervalHighlight;
620 private Color currentLineHighlight;
621 private Color foldColor;
622
623 private FontMetrics fm;
624
625 private int alignment;
626
627 private int interval;
628 private boolean currentLineHighlightEnabled;
629 private boolean expanded;
630
631 private boolean bracketHighlight;
632 private Color bracketHighlightColor;
633
634 private int borderWidth;
635 private Border focusBorder, noFocusBorder;
636 //}}}
637
638 //{{{ MouseHandler class
639 class MouseHandler extends MouseInputAdapter
640 {
641 boolean drag;
642 int toolTipInitialDelay, toolTipReshowDelay;
643
644 //{{{ mouseEntered() method
645 public void mouseEntered(MouseEvent e)
646 {
647 ToolTipManager ttm = ToolTipManager.sharedInstance();
648 toolTipInitialDelay = ttm.getInitialDelay();
649 toolTipReshowDelay = ttm.getReshowDelay();
650 ttm.setInitialDelay(0);
651 ttm.setReshowDelay(0);
652 } //}}}
653
654 //{{{ mouseExited() method
655 public void mouseExited(MouseEvent evt)
656 {
657 ToolTipManager ttm = ToolTipManager.sharedInstance();
658 ttm.setInitialDelay(toolTipInitialDelay);
659 ttm.setReshowDelay(toolTipReshowDelay);
660 } //}}}
661
662 //{{{ mousePressed() method
663 public void mousePressed(MouseEvent e)
664 {
665 if(e.getX() < getWidth() - borderWidth * 2)
666 {
667 Buffer buffer = textArea.getBuffer();
668
669 int line = e.getY() / textArea.getPainter()
670 .getFontMetrics().getHeight()
671 + textArea.getFirstLine();
672
673 FoldVisibilityManager foldVisibilityManager
674 = textArea.getFoldVisibilityManager();
675
676 if(line > foldVisibilityManager.getVirtualLineCount() - 1)
677 return;
678
679 line = foldVisibilityManager.virtualToPhysical(line);
680 //{{{ Clicking on fold triangle does various things
681 if(buffer.isFoldStart(line))
682 {
683 if(e.isControlDown())
684 {
685 foldVisibilityManager
686 .expandFold(line,true);
687 textArea.selectFold(line);
688 }
689 else if(foldVisibilityManager
690 .isLineVisible(line + 1))
691 {
692 foldVisibilityManager
693 .collapseFold(line);
694 }
695 else
696 {
697 foldVisibilityManager
698 .expandFold(line,
699 e.isShiftDown());
700 }
701 } //}}}
702 //{{{ Clicking in bracket scope locates matching bracket
703 else if(bracketHighlight)
704 {
705 if(textArea.isBracketHighlightVisible())
706 {
707 int bracketLine = textArea.getBracketLine();
708 int caretLine = textArea.getCaretLine();
709 if(caretLine != bracketLine)
710 {
711 if(caretLine > bracketLine)
712 {
713 int tmp = caretLine;
714 caretLine = bracketLine;
715 bracketLine = tmp;
716 }
717
718 if(line >= caretLine
719 && line <= bracketLine)
720 {
721 if(e.isControlDown())
722 textArea.selectToMatchingBracket();
723 else
724 textArea.goToMatchingBracket();
725 }
726 }
727 }
728 } //}}}
729 }
730 else
731 {
732 e.translatePoint(-getWidth(),0);
733 textArea.mouseHandler.mousePressed(e);
734 drag = true;
735 }
736 } //}}}
737
738 //{{{ mouseDragged() method
739 public void mouseDragged(MouseEvent e)
740 {
741 if(drag && e.getX() >= getWidth() - borderWidth * 2)
742 {
743 e.translatePoint(-getWidth(),0);
744 textArea.mouseHandler.mouseDragged(e);
745 }
746 } //}}}
747
748 //{{{ mouseReleased() method
749 public void mouseReleased(MouseEvent e)
750 {
751 if(drag && e.getX() >= getWidth() - borderWidth * 2)
752 {
753 e.translatePoint(-getWidth(),0);
754 textArea.mouseHandler.mouseReleased(e);
755 }
756
757 drag = false;
758 } //}}}
759 } //}}}
760}