PageRenderTime 71ms CodeModel.GetById 25ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/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}