PageRenderTime 75ms CodeModel.GetById 2ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/textarea/Gutter.java

#
Java | 1131 lines | 759 code | 129 blank | 243 comment | 137 complexity | be3b6b4388ff3e03b3f9f359391bedc3 MD5 | raw file
   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, 2002 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 javax.swing.*;
  30import javax.swing.border.*;
  31import javax.swing.event.*;
  32
  33import org.gjt.sp.jedit.buffer.BufferAdapter;
  34import org.gjt.sp.jedit.buffer.BufferListener;
  35import org.gjt.sp.jedit.buffer.JEditBuffer;
  36import org.gjt.sp.util.Log;
  37//}}}
  38
  39/**
  40 * The gutter is the component that displays folding indicators and line
  41 * numbers to the left of the text area. The only methods in this class
  42 * that should be called by plugins are those for adding and removing
  43 * text area extensions.
  44 *
  45 * @see #addExtension(TextAreaExtension)
  46 * @see #addExtension(int,TextAreaExtension)
  47 * @see #removeExtension(TextAreaExtension)
  48 * @see TextAreaExtension
  49 * @see TextArea
  50 *
  51 * @author Mike Dillon and Slava Pestov
  52 * @version $Id: Gutter.java 18808 2010-10-21 20:54:15Z daleanson $
  53 */
  54public class Gutter extends JComponent implements SwingConstants
  55{
  56	//{{{ Layers
  57	/**
  58	 * The lowest possible layer.
  59	 * @see #addExtension(int,TextAreaExtension)
  60	 * @since jEdit 4.0pre4
  61	 */
  62	public static final int LOWEST_LAYER = Integer.MIN_VALUE;
  63
  64	/**
  65	 * Default extension layer. This is above the wrap guide but below the
  66	 * bracket highlight.
  67	 * @since jEdit 4.0pre4
  68	 */
  69	public static final int DEFAULT_LAYER = 0;
  70
  71	/**
  72	 * Highest possible layer.
  73	 * @since jEdit 4.0pre4
  74	 */
  75	public static final int HIGHEST_LAYER = Integer.MAX_VALUE;
  76	//}}}
  77
  78	//{{{ Fold painters
  79	/**
  80	 * Fold painter service.
  81	 * @since jEdit 4.3pre16
  82	 */
  83	public static final String FOLD_PAINTER_PROPERTY = "foldPainter";
  84	public static final String FOLD_PAINTER_SERVICE = "org.gjt.sp.jedit.textarea.FoldPainter";
  85	public static final String DEFAULT_FOLD_PAINTER_SERVICE = "Triangle";
  86
  87	//{{{ setFolderPainter() method
  88	public void setFoldPainter(FoldPainter painter)
  89	{
  90		if (painter == null)
  91			foldPainter = new TriangleFoldPainter();
  92		else
  93			foldPainter = painter;
  94	}
  95	//}}}
  96	
  97	//}}} Fold painters
  98	
  99	//{{{ Gutter constructor
 100	public Gutter(TextArea textArea)
 101	{
 102		this.textArea = textArea;
 103		enabled = true;
 104		selectionAreaEnabled = true;
 105		selectionAreaWidth = SELECTION_GUTTER_WIDTH;
 106
 107		setAutoscrolls(true);
 108		setOpaque(true);
 109		setRequestFocusEnabled(false);
 110
 111		extensionMgr = new ExtensionManager();
 112
 113		mouseHandler = new MouseHandler();
 114		addMouseListener(mouseHandler);
 115		addMouseMotionListener(mouseHandler);
 116
 117		bufferListener = new BufferAdapter()
 118		{
 119			public void bufferLoaded(JEditBuffer buffer)
 120			{
 121				updateLineNumberWidth();
 122			}
 123
 124			public void contentInserted(JEditBuffer buffer, int startLine,
 125					int offset, int numLines, int length)
 126			{
 127				updateLineNumberWidth();
 128			}
 129
 130			public void contentRemoved(JEditBuffer buffer, int startLine,
 131					int offset, int numLines, int length) 
 132			{
 133				updateLineNumberWidth();
 134			}
 135		};
 136
 137		updateBorder();
 138		setFoldPainter(textArea.getFoldPainter());
 139	} //}}}
 140
 141	//{{{ paintComponent() method
 142	public void paintComponent(Graphics _gfx)
 143	{
 144		Graphics2D gfx = (Graphics2D)_gfx;
 145		gfx.setRenderingHints(textArea.getPainter().renderingHints);
 146		// fill the background
 147		Rectangle clip = gfx.getClipBounds();
 148		gfx.setColor(getBackground());
 149		int bgColorWidth = isSelectionAreaEnabled() ? FOLD_MARKER_SIZE :
 150			clip.width; 
 151		gfx.fillRect(clip.x, clip.y, bgColorWidth, clip.height);
 152		if (isSelectionAreaEnabled())
 153		{
 154			if (selectionAreaBgColor == null)
 155				selectionAreaBgColor = getBackground();
 156			gfx.setColor(selectionAreaBgColor);
 157			gfx.fillRect(clip.x + FOLD_MARKER_SIZE, clip.y,
 158				clip.width - FOLD_MARKER_SIZE, clip.height);
 159		}
 160		// if buffer is loading, don't paint anything
 161		if (textArea.getBuffer().isLoading())
 162			return;
 163
 164		int lineHeight = textArea.getPainter().getLineHeight();
 165
 166		if(lineHeight == 0)
 167			return;
 168
 169		int firstLine = clip.y / lineHeight;
 170		int lastLine = (clip.y + clip.height - 1) / lineHeight;
 171
 172		if(lastLine - firstLine > textArea.getVisibleLines())
 173		{
 174			Log.log(Log.ERROR,this,"BUG: firstLine=" + firstLine);
 175			Log.log(Log.ERROR,this,"     lastLine=" + lastLine);
 176			Log.log(Log.ERROR,this,"     visibleLines=" + textArea.getVisibleLines());
 177			Log.log(Log.ERROR,this,"     height=" + getHeight());
 178			Log.log(Log.ERROR,this,"     painter.height=" + lineHeight);
 179			Log.log(Log.ERROR,this,"     clip.y=" + clip.y);
 180			Log.log(Log.ERROR,this,"     clip.height=" + clip.height);
 181			Log.log(Log.ERROR,this,"     lineHeight=" + lineHeight);
 182		}
 183	
 184		int y = clip.y - clip.y % lineHeight;
 185
 186		extensionMgr.paintScreenLineRange(textArea,gfx,
 187			firstLine,lastLine,y,lineHeight);
 188
 189		for (int line = firstLine; line <= lastLine;
 190			line++, y += lineHeight)
 191		{
 192			paintLine(gfx,line,y);
 193		}
 194	} //}}}
 195
 196	//{{{ addExtension() method
 197	/**
 198	 * Adds a text area extension, which can perform custom painting and
 199	 * tool tip handling.
 200	 * @param extension The extension
 201	 * @since jEdit 4.0pre4
 202	 */
 203	public void addExtension(TextAreaExtension extension)
 204	{
 205		extensionMgr.addExtension(DEFAULT_LAYER,extension);
 206		repaint();
 207	} //}}}
 208
 209	//{{{ addExtension() method
 210	/**
 211	 * Adds a text area extension, which can perform custom painting and
 212	 * tool tip handling.
 213	 * @param layer The layer to add the extension to. Note that more than
 214	 * extension can share the same layer.
 215	 * @param extension The extension
 216	 * @since jEdit 4.0pre4
 217	 */
 218	public void addExtension(int layer, TextAreaExtension extension)
 219	{
 220		extensionMgr.addExtension(layer,extension);
 221		repaint();
 222	} //}}}
 223
 224	//{{{ removeExtension() method
 225	/**
 226	 * Removes a text area extension. It will no longer be asked to
 227	 * perform custom painting and tool tip handling.
 228	 * @param extension The extension
 229	 * @since jEdit 4.0pre4
 230	 */
 231	public void removeExtension(TextAreaExtension extension)
 232	{
 233		extensionMgr.removeExtension(extension);
 234		repaint();
 235	} //}}}
 236
 237	//{{{ getExtensions() method
 238	/**
 239	 * Returns an array of registered text area extensions. Useful for
 240	 * debugging purposes.
 241	 * @since jEdit 4.1pre5
 242	 */
 243	public TextAreaExtension[] getExtensions()
 244	{
 245		return extensionMgr.getExtensions();
 246	} //}}}
 247
 248	//{{{ getToolTipText() method
 249	/**
 250	 * Returns the tool tip to display at the specified location.
 251	 * @param evt The mouse event
 252	 */
 253	public String getToolTipText(MouseEvent evt)
 254	{
 255		if(textArea.getBuffer().isLoading())
 256			return null;
 257
 258		return extensionMgr.getToolTipText(evt.getX(),evt.getY());
 259	} //}}}
 260
 261	//{{{ setBorder() method
 262	/**
 263	 * Convenience method for setting a default matte border on the right
 264	 * with the specified border width and color
 265	 * @param width The border width (in pixels)
 266	 * @param color1 The focused border color
 267	 * @param color2 The unfocused border color
 268	 * @param color3 The gutter/text area gap color
 269	 */
 270	public void setBorder(int width, Color color1, Color color2, Color color3)
 271	{
 272		borderWidth = width;
 273
 274		focusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
 275			new MatteBorder(0,0,0,width,color1));
 276		noFocusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
 277			new MatteBorder(0,0,0,width,color2));
 278		updateBorder();
 279	} //}}}
 280
 281	//{{{ updateBorder() method
 282	/**
 283	 * Sets the border differently if the text area has focus or not.
 284	 */
 285	public void updateBorder()
 286	{
 287		if (textArea.hasFocus())
 288			setBorder(focusBorder);
 289		else
 290			setBorder(noFocusBorder);
 291	} //}}}
 292
 293	//{{{ setBorder() method
 294	/*
 295	 * JComponent.setBorder(Border) is overridden here to cache the left
 296	 * inset of the border (if any) to avoid having to fetch it during every
 297	 * repaint.
 298	 */
 299	public void setBorder(Border border)
 300	{
 301		super.setBorder(border);
 302
 303		if (border == null)
 304		{
 305			collapsedSize.width = 0;
 306			collapsedSize.height = 0;
 307		}
 308		else
 309		{
 310			Insets insets = border.getBorderInsets(this);
 311			collapsedSize.width = FOLD_MARKER_SIZE + insets.right;
 312			if (isSelectionAreaEnabled())
 313				 collapsedSize.width += selectionAreaWidth;
 314			collapsedSize.height = gutterSize.height
 315				= insets.top + insets.bottom;
 316			lineNumberWidth = fm.charWidth('5') * getLineNumberDigitCount(); 
 317			gutterSize.width = FOLD_MARKER_SIZE + insets.right
 318				+ lineNumberWidth;
 319		}
 320
 321		revalidate();
 322	} //}}}
 323
 324	//{{{ setMinLineNumberDigitCount() method
 325	public void setMinLineNumberDigitCount(int min)
 326	{
 327		if (min == minLineNumberDigits)
 328			return;
 329		minLineNumberDigits = min;
 330		if (textArea.getBuffer() != null)
 331			updateLineNumberWidth();
 332	} //}}}
 333
 334	//{{{ getMinLineNumberDigitCount() method
 335	private int getMinLineNumberDigitCount()
 336	{
 337		return minLineNumberDigits;
 338	} //}}}
 339
 340	//{{{ getLineNumberDigitCount() method
 341	private int getLineNumberDigitCount()
 342	{
 343		JEditBuffer buf = textArea.getBuffer();
 344		int minDigits = getMinLineNumberDigitCount();
 345		if (buf == null)
 346			return minDigits;
 347		int count = buf.getLineCount();
 348		int digits;
 349		for (digits = 0; count > 0; digits++)
 350			count /= 10;
 351		return (digits < minDigits) ? minDigits : digits;
 352	} //}}}
 353
 354	//{{{ setBuffer() method
 355	void setBuffer(JEditBuffer newBuffer)
 356	{
 357		if (buffer != null)
 358			buffer.removeBufferListener(bufferListener);
 359		buffer = newBuffer;
 360		if (buffer != null)
 361			buffer.addBufferListener(bufferListener);
 362		updateLineNumberWidth();
 363	} //}}}
 364
 365	//{{{ updateLineNumberWidth() method
 366	private void updateLineNumberWidth()
 367	{
 368		Font f = getFont();
 369		if (f != null)
 370			setFont(getFont());
 371	} //}}}
 372
 373	//{{{ dispose() method
 374	void dispose()
 375	{
 376		if (buffer != null)
 377		{
 378			buffer.removeBufferListener(bufferListener);
 379			buffer = null;
 380		}
 381	} //}}}
 382
 383	//{{{ setFont() method
 384	/*
 385	 * JComponent.setFont(Font) is overridden here to cache the font
 386	 * metrics for the font. This avoids having to get the font metrics
 387	 * during every repaint.
 388	 */
 389	public void setFont(Font font)
 390	{
 391		super.setFont(font);
 392
 393		fm = getFontMetrics(font);
 394
 395		Border border = getBorder();
 396		if(border != null)
 397		{
 398			lineNumberWidth = fm.charWidth('5') * getLineNumberDigitCount(); 
 399			gutterSize.width = FOLD_MARKER_SIZE
 400				+ border.getBorderInsets(this).right
 401				+ lineNumberWidth;
 402			revalidate();
 403		}
 404	} //}}}
 405
 406	//{{{ Getters and setters
 407
 408	//{{{ setGutterEnabled() method
 409	/* Enables showing or hiding the gutter. */
 410	public void setGutterEnabled(boolean enabled)
 411	{
 412		this.enabled = enabled;
 413		revalidate();
 414	} //}}}
 415
 416	//{{{ isSelectionAreaEnabled() method
 417	public boolean isSelectionAreaEnabled()
 418	{
 419		return selectionAreaEnabled;
 420	} //}}}
 421
 422	//{{{ setSelectionAreaEnabled() method
 423	public void setSelectionAreaEnabled(boolean enabled)
 424	{
 425		if (isSelectionAreaEnabled() == enabled)
 426			return;
 427		selectionAreaEnabled = enabled;
 428		if (enabled)
 429			collapsedSize.width += selectionAreaWidth;
 430		else
 431			collapsedSize.width -= selectionAreaWidth;
 432		revalidate();
 433	} //}}}
 434
 435	//{{{ setSelectionAreaBackground() method
 436	public void setSelectionAreaBackground(Color bgColor)
 437	{
 438		selectionAreaBgColor = bgColor;
 439		repaint();
 440	} //}}}
 441
 442	//{{{ setSelectionAreaWidth() method
 443	public void setSelectionAreaWidth(int width)
 444	{
 445		selectionAreaWidth = width;
 446		revalidate();
 447	} //}}}
 448
 449	//{{{ getHighlightedForeground() method
 450	/**
 451	 * Get the foreground color for highlighted line numbers
 452	 * @return The highlight color
 453	 */
 454	public Color getHighlightedForeground()
 455	{
 456		return intervalHighlight;
 457	} //}}}
 458
 459	//{{{ setHighlightedForeground() method
 460	public void setHighlightedForeground(Color highlight)
 461	{
 462		intervalHighlight = highlight;
 463	} //}}}
 464
 465	//{{{ getCurrentLineForeground() method
 466	public Color getCurrentLineForeground()
 467 	{
 468		return currentLineHighlight;
 469	} //}}}
 470
 471	//{{{ setCurrentLineForeground() method
 472	public void setCurrentLineForeground(Color highlight)
 473	{
 474		currentLineHighlight = highlight;
 475 	} //}}}
 476
 477	//{{{ getFoldColor() method
 478	public Color getFoldColor()
 479 	{
 480		return foldColor;
 481	} //}}}
 482
 483	//{{{ setFoldColor() method
 484	public void setFoldColor(Color foldColor)
 485	{
 486		this.foldColor = foldColor;
 487 	} //}}}
 488
 489	//{{{ getPreferredSize() method
 490	/*
 491	 * Component.getPreferredSize() is overridden here to support the
 492	 * collapsing behavior.
 493	 */
 494	public Dimension getPreferredSize()
 495	{
 496		if (! enabled)
 497			return disabledSize;
 498		if (expanded)
 499			return gutterSize;
 500		else
 501			return collapsedSize;
 502	} //}}}
 503
 504	//{{{ getMinimumSize() method
 505	public Dimension getMinimumSize()
 506	{
 507		return getPreferredSize();
 508	} //}}}
 509
 510	//{{{ getLineNumberAlignment() method
 511	/**
 512	 * Identifies whether the horizontal alignment of the line numbers.
 513	 * @return Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
 514	 */
 515	public int getLineNumberAlignment()
 516	{
 517		return alignment;
 518	} //}}}
 519
 520	//{{{ setLineNumberAlignment() method
 521	/**
 522	 * Sets the horizontal alignment of the line numbers.
 523	 * @param alignment Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
 524	 */
 525	public void setLineNumberAlignment(int alignment)
 526	{
 527		if (this.alignment == alignment) return;
 528
 529		this.alignment = alignment;
 530
 531		repaint();
 532	} //}}}
 533
 534	//{{{ isExpanded() method
 535	/**
 536	 * Identifies whether the gutter is collapsed or expanded.
 537	 * @return true if the gutter is expanded, false if it is collapsed
 538	 */
 539	public boolean isExpanded()
 540	{
 541		return expanded;
 542	} //}}}
 543
 544	//{{{ setExpanded() method
 545	/**
 546	 * Sets whether the gutter is collapsed or expanded and force the text
 547	 * area to update its layout if there is a change.
 548	 * @param expanded true if the gutter is expanded,
 549	 *                   false if it is collapsed
 550	 */
 551	public void setExpanded(boolean expanded)
 552	{
 553		if (this.expanded == expanded) return;
 554
 555		this.expanded = expanded;
 556
 557		textArea.revalidate();
 558	} //}}}
 559
 560	//{{{ toggleExpanded() method
 561	/**
 562	 * Toggles whether the gutter is collapsed or expanded.
 563	 */
 564	public void toggleExpanded()
 565	{
 566		setExpanded(!expanded);
 567	} //}}}
 568
 569	//{{{ getHighlightInterval() method
 570	/**
 571	 * Sets the number of lines between highlighted line numbers.
 572	 * @return The number of lines between highlighted line numbers or
 573	 *          zero if highlighting is disabled
 574	 */
 575	public int getHighlightInterval()
 576	{
 577		return interval;
 578	} //}}}
 579
 580	//{{{ setHighlightInterval() method
 581	/**
 582	 * Sets the number of lines between highlighted line numbers. Any value
 583	 * less than or equal to one will result in highlighting being disabled.
 584	 * @param interval The number of lines between highlighted line numbers
 585	 */
 586	public void setHighlightInterval(int interval)
 587	{
 588		if (interval <= 1) interval = 0;
 589		this.interval = interval;
 590		repaint();
 591	} //}}}
 592
 593	//{{{ isCurrentLineHighlightEnabled() method
 594	public boolean isCurrentLineHighlightEnabled()
 595	{
 596		return currentLineHighlightEnabled;
 597	} //}}}
 598
 599	//{{{ setCurrentLineHighlightEnabled() method
 600	public void setCurrentLineHighlightEnabled(boolean enabled)
 601	{
 602		if (currentLineHighlightEnabled == enabled) return;
 603
 604		currentLineHighlightEnabled = enabled;
 605
 606		repaint();
 607	} //}}}
 608
 609	//{{{ getStructureHighlightColor() method
 610	/**
 611	 * Returns the structure highlight color.
 612	 * @since jEdit 4.2pre3
 613	 */
 614	public final Color getStructureHighlightColor()
 615	{
 616		return structureHighlightColor;
 617	} //}}}
 618
 619	//{{{ setStructureHighlightColor() method
 620	/**
 621	 * Sets the structure highlight color.
 622	 * @param structureHighlightColor The structure highlight color
 623	 * @since jEdit 4.2pre3
 624	 */
 625	public final void setStructureHighlightColor(Color structureHighlightColor)
 626	{
 627		this.structureHighlightColor = structureHighlightColor;
 628		repaint();
 629	} //}}}
 630
 631	//{{{ isStructureHighlightEnabled() method
 632	/**
 633	 * Returns true if structure highlighting is enabled, false otherwise.
 634	 * @since jEdit 4.2pre3
 635	 */
 636	public final boolean isStructureHighlightEnabled()
 637	{
 638		return structureHighlight;
 639	} //}}}
 640
 641	//{{{ setStructureHighlightEnabled() method
 642	/**
 643	 * Enables or disables structure highlighting.
 644	 * @param structureHighlight True if structure highlighting should be
 645	 * enabled, false otherwise
 646	 * @since jEdit 4.2pre3
 647	 */
 648	public final void setStructureHighlightEnabled(boolean structureHighlight)
 649	{
 650		this.structureHighlight = structureHighlight;
 651		repaint();
 652	} //}}}
 653
 654	public void setSelectionPopupHandler(GutterPopupHandler handler)
 655	{
 656		mouseHandler.selectionPopupHandler = handler;
 657	}
 658
 659	public GutterPopupHandler getSelectionPopupHandler()
 660	{
 661		return mouseHandler.selectionPopupHandler;
 662	}
 663
 664	public void setMouseActionsProvider(MouseActionsProvider mouseActionsProvider)
 665	{
 666		mouseHandler.mouseActions = mouseActionsProvider;
 667	}
 668	//}}}
 669
 670	//{{{ Private members
 671
 672	//{{{ Instance variables
 673	private static final int FOLD_MARKER_SIZE = 12;
 674	private static final int SELECTION_GUTTER_WIDTH = 12;
 675		// The selection gutter exists only if the gutter is not expanded
 676
 677	private boolean enabled;
 678	private final TextArea textArea;
 679	private MouseHandler mouseHandler;
 680	private ExtensionManager extensionMgr;
 681
 682	private Dimension gutterSize = new Dimension(0,0);
 683	private Dimension collapsedSize = new Dimension(0,0);
 684	private int lineNumberWidth;
 685	private Dimension disabledSize = new Dimension(0,0);
 686
 687	private Color intervalHighlight;
 688	private Color currentLineHighlight;
 689	private Color foldColor;
 690	private Color selectionAreaBgColor;
 691
 692	private FontMetrics fm;
 693
 694	private int alignment;
 695
 696	private int interval;
 697	private boolean currentLineHighlightEnabled;
 698	private boolean expanded;
 699	private boolean selectionAreaEnabled;
 700
 701	private boolean structureHighlight;
 702	private Color structureHighlightColor;
 703
 704	private int borderWidth;
 705	private Border focusBorder, noFocusBorder;
 706	
 707	private FoldPainter foldPainter;
 708	private JEditBuffer buffer;
 709	private BufferListener bufferListener;
 710	private int minLineNumberDigits;
 711	private int selectionAreaWidth;
 712	//}}}
 713
 714	//{{{ paintLine() method
 715	private void paintLine(Graphics2D gfx, int line, int y)
 716	{
 717		JEditBuffer buffer = textArea.getBuffer();
 718		if(buffer.isLoading())
 719			return;
 720
 721		FontMetrics textAreaFm = textArea.getPainter().getFontMetrics();
 722		int lineHeight = textArea.getPainter().getLineHeight();
 723		int baseline = textAreaFm.getAscent();
 724
 725		ChunkCache.LineInfo info = textArea.chunkCache.getLineInfo(line);
 726		int physicalLine = info.physicalLine;
 727
 728		// Skip lines beyond EOF
 729		if(physicalLine == -1)
 730			return;
 731
 732		boolean drawFoldMiddle = true;
 733		//{{{ Paint fold start and end indicators
 734		if(info.firstSubregion && buffer.isFoldStart(physicalLine))
 735		{
 736			drawFoldMiddle = false;
 737			foldPainter.paintFoldStart(this, gfx, line, physicalLine,
 738					textArea.displayManager.isLineVisible(physicalLine+1),
 739					y, lineHeight, buffer);
 740		}
 741		else if(info.lastSubregion && buffer.isFoldEnd(physicalLine))
 742		{
 743			drawFoldMiddle = false;
 744			foldPainter.paintFoldEnd(this, gfx, line, physicalLine, y,
 745					lineHeight, buffer);
 746		} //}}}
 747		//{{{ Paint bracket scope
 748		else if(structureHighlight)
 749		{
 750			StructureMatcher.Match match = textArea.getStructureMatch();
 751			int caretLine = textArea.getCaretLine();
 752
 753			if(textArea.isStructureHighlightVisible()
 754				&& physicalLine >= Math.min(caretLine,match.startLine)
 755				&& physicalLine <= Math.max(caretLine,match.startLine))
 756			{
 757				int caretScreenLine;
 758				if(caretLine > textArea.getLastPhysicalLine())
 759					caretScreenLine = Integer.MAX_VALUE;
 760				else if(textArea.displayManager.isLineVisible(
 761						textArea.getCaretLine()))
 762				{
 763					caretScreenLine = textArea
 764						.getScreenLineOfOffset(
 765						textArea.getCaretPosition());
 766				}
 767				else
 768				{
 769					caretScreenLine = -1;
 770				}
 771
 772				int structScreenLine;
 773				if(match.startLine > textArea.getLastPhysicalLine())
 774					structScreenLine = Integer.MAX_VALUE;
 775				else if(textArea.displayManager.isLineVisible(
 776						match.startLine))
 777				{
 778					structScreenLine = textArea
 779						.getScreenLineOfOffset(
 780						match.start);
 781				}
 782				else
 783				{
 784					structScreenLine = -1;
 785				}
 786
 787				if(caretScreenLine > structScreenLine)
 788				{
 789					int tmp = caretScreenLine;
 790					caretScreenLine = structScreenLine;
 791					structScreenLine = tmp;
 792				}
 793
 794				gfx.setColor(structureHighlightColor);
 795				drawFoldMiddle = false;
 796				if(structScreenLine == caretScreenLine)
 797				{
 798					// do nothing
 799					drawFoldMiddle = true;
 800				}
 801				// draw |^
 802				else if(line == caretScreenLine)
 803				{
 804					gfx.fillRect(5,
 805						y
 806						+ lineHeight / 2,
 807						5,
 808						2);
 809					gfx.fillRect(5,
 810						y
 811						+ lineHeight / 2,
 812						2,
 813						lineHeight - lineHeight / 2);
 814				}
 815				// draw |_
 816				else if(line == structScreenLine)
 817				{
 818					gfx.fillRect(5,
 819						y,
 820						2,
 821						lineHeight / 2);
 822					gfx.fillRect(5,
 823						y + lineHeight / 2,
 824						5,
 825						2);
 826				}
 827				// draw |
 828				else if(line > caretScreenLine
 829					&& line < structScreenLine)
 830				{
 831					gfx.fillRect(5,
 832						y,
 833						2,
 834						lineHeight);
 835				}
 836			}
 837		} //}}}
 838		if(drawFoldMiddle && buffer.getFoldLevel(physicalLine) > 0)
 839		{
 840			foldPainter.paintFoldMiddle(this, gfx, line, physicalLine,
 841					y, lineHeight, buffer);
 842		}
 843
 844		//{{{ Paint line numbers
 845		if(info.firstSubregion && expanded)
 846		{
 847			String number = Integer.toString(physicalLine + 1);
 848
 849			int offset;
 850			switch (alignment)
 851			{
 852			case RIGHT:
 853				offset = lineNumberWidth - (fm.stringWidth(number) + 1);
 854				break;
 855			case CENTER:
 856				offset = (lineNumberWidth - fm.stringWidth(number)) / 2;
 857				break;
 858			case LEFT: default:
 859				offset = 0;
 860				break;
 861			}
 862
 863			if (physicalLine == textArea.getCaretLine() && currentLineHighlightEnabled)
 864			{
 865				gfx.setColor(currentLineHighlight);
 866			}
 867			else if (interval > 1 && (physicalLine + 1) % interval == 0)
 868				gfx.setColor(intervalHighlight);
 869			else
 870				gfx.setColor(getForeground());
 871
 872			gfx.drawString(number, FOLD_MARKER_SIZE + offset,
 873				baseline + y);
 874		} //}}}
 875	} //}}}
 876
 877	//}}}
 878
 879	//{{{ MouseHandler class
 880	class MouseHandler extends MouseInputAdapter
 881	{
 882		MouseActionsProvider mouseActions;
 883		boolean drag;
 884		int toolTipInitialDelay, toolTipReshowDelay;
 885		boolean selectLines;
 886		int selAnchorLine;
 887		GutterPopupHandler selectionPopupHandler;
 888
 889		//{{{ mouseEntered() method
 890		public void mouseEntered(MouseEvent e)
 891		{
 892			ToolTipManager ttm = ToolTipManager.sharedInstance();
 893			toolTipInitialDelay = ttm.getInitialDelay();
 894			toolTipReshowDelay = ttm.getReshowDelay();
 895			ttm.setInitialDelay(0);
 896			ttm.setReshowDelay(0);
 897		} //}}}
 898
 899		//{{{ mouseExited() method
 900		public void mouseExited(MouseEvent evt)
 901		{
 902			ToolTipManager ttm = ToolTipManager.sharedInstance();
 903			ttm.setInitialDelay(toolTipInitialDelay);
 904			ttm.setReshowDelay(toolTipReshowDelay);
 905		} //}}}
 906
 907		//{{{ mousePressed() method
 908		public void mousePressed(MouseEvent e)
 909		{
 910			textArea.requestFocus();
 911
 912			boolean outsideGutter =
 913				(e.getX() >= getWidth() - borderWidth * 2);
 914			if(TextAreaMouseHandler.isPopupTrigger(e) || outsideGutter)
 915			{
 916				if ((selectionPopupHandler != null) &&
 917					(! outsideGutter) &&
 918					(e.getX() > FOLD_MARKER_SIZE))
 919				{
 920					int screenLine = e.getY() / textArea.getPainter().getLineHeight();
 921					int line = textArea.chunkCache.getLineInfo(screenLine)
 922						.physicalLine;
 923					if (line >= 0)
 924					{
 925						selectionPopupHandler.handlePopup(
 926							e.getX(), e.getY(), line);
 927						return;
 928					}
 929				}
 930				e.translatePoint(-getWidth(),0);
 931				textArea.mouseHandler.mousePressed(e);
 932				drag = true;
 933			}
 934			else
 935			{
 936				JEditBuffer buffer = textArea.getBuffer();
 937
 938				int screenLine = e.getY() / textArea.getPainter().getLineHeight();
 939
 940				int line = textArea.chunkCache.getLineInfo(screenLine)
 941					.physicalLine;
 942
 943				if(line == -1)
 944					return;
 945
 946				if (e.getX() >= FOLD_MARKER_SIZE)
 947				{
 948					Selection s = new Selection.Range(
 949						textArea.getLineStartOffset(line),
 950						getFoldEndOffset(line));
 951					if(textArea.isMultipleSelectionEnabled())
 952						textArea.addToSelection(s);
 953					else
 954						textArea.setSelection(s);
 955					selectLines = true;
 956					selAnchorLine = line;
 957					return;
 958				}
 959
 960				//{{{ Determine action
 961				String defaultAction;
 962				String variant;
 963				if(buffer.isFoldStart(line))
 964				{
 965					defaultAction = "toggle-fold";
 966					variant = "fold";
 967				}
 968				else if(structureHighlight
 969					&& textArea.isStructureHighlightVisible()
 970					&& textArea.lineInStructureScope(line))
 971				{
 972					defaultAction = "match-struct";
 973					variant = "struct";
 974				}
 975				else
 976					return;
 977
 978				String action = null;
 979
 980				if (mouseActions != null)
 981					action = mouseActions.getActionForEvent(
 982						e,variant);
 983
 984				if(action == null)
 985					action = defaultAction;
 986				//}}}
 987
 988				//{{{ Handle actions
 989				StructureMatcher.Match match = textArea
 990					.getStructureMatch();
 991
 992				if(action.equals("select-fold"))
 993				{
 994					textArea.displayManager.expandFold(line,true);
 995					textArea.selectFold(line);
 996				}
 997				else if(action.equals("narrow-fold"))
 998				{
 999					int[] lines = buffer.getFoldAtLine(line);
1000					textArea.displayManager.narrow(lines[0],lines[1]);
1001				}
1002				else if(action.startsWith("toggle-fold"))
1003				{
1004					if(textArea.displayManager
1005						.isLineVisible(line + 1))
1006					{
1007						textArea.collapseFold(line);
1008					}
1009					else
1010					{
1011						if(action.endsWith("-fully"))
1012						{
1013							textArea.displayManager
1014								.expandFold(line,
1015								true);
1016						}
1017						else
1018						{
1019							textArea.displayManager
1020								.expandFold(line,
1021								false);
1022						}
1023					}
1024				}
1025				else if(action.equals("match-struct"))
1026				{
1027					if(match != null)
1028						textArea.setCaretPosition(match.end);
1029				}
1030				else if(action.equals("select-struct"))
1031				{
1032					if(match != null)
1033					{
1034						match.matcher.selectMatch(
1035							textArea);
1036					}
1037				}
1038				else if(action.equals("narrow-struct"))
1039				{
1040					if(match != null)
1041					{
1042						int start = Math.min(
1043							match.startLine,
1044							textArea.getCaretLine());
1045						int end = Math.max(
1046							match.endLine,
1047							textArea.getCaretLine());
1048						textArea.displayManager.narrow(start,end);
1049					}
1050				} //}}}
1051			}
1052		} //}}}
1053
1054		//{{{ mouseDragged() method
1055		public void mouseDragged(MouseEvent e)
1056		{
1057			if(drag /* && e.getX() >= getWidth() - borderWidth * 2 */)
1058			{
1059				e.translatePoint(-getWidth(),0);
1060				textArea.mouseHandler.mouseDragged(e);
1061			}
1062			else if(selectLines)
1063			{
1064				int screenLine = e.getY() / textArea.getPainter().getLineHeight();
1065				int line;
1066				if(e.getY() < 0)
1067				{
1068					textArea.scrollUpLine();
1069					line = textArea.getFirstPhysicalLine();
1070				}
1071				else if(e.getY() >= getHeight())
1072				{
1073					textArea.scrollDownLine();
1074					line = textArea.getLastPhysicalLine();
1075				}
1076				else
1077					line = textArea.chunkCache.getLineInfo(screenLine)
1078						.physicalLine;
1079
1080				int selStart, selEnd;
1081				if(line < selAnchorLine)
1082				{
1083					selStart = textArea.getLineStartOffset(line);
1084					selEnd = getFoldEndOffset(selAnchorLine);
1085				}
1086				else
1087				{
1088					selStart = textArea.getLineStartOffset(selAnchorLine);
1089					selEnd = getFoldEndOffset(line);
1090				}
1091
1092				textArea.resizeSelection(selStart, selEnd, 0, false);
1093			}
1094		} //}}}
1095
1096		//{{{ getFoldEndOffset() method
1097		private int getFoldEndOffset(int line)
1098		{
1099			JEditBuffer buffer = textArea.getBuffer();
1100			int endLine;
1101			if ((line == buffer.getLineCount() - 1) ||
1102				(textArea.displayManager.isLineVisible(line + 1)))
1103			{
1104				endLine = line;
1105			}
1106			else
1107			{
1108				int[] lines = buffer.getFoldAtLine(line);
1109				endLine = lines[1];
1110			}
1111
1112			if(endLine == buffer.getLineCount() - 1)
1113				return buffer.getLineEndOffset(endLine) - 1;
1114			else
1115				return buffer.getLineEndOffset(endLine);
1116		} //}}}
1117
1118		//{{{ mouseReleased() method
1119		public void mouseReleased(MouseEvent e)
1120		{
1121			if(drag && e.getX() >= getWidth() - borderWidth * 2)
1122			{
1123				e.translatePoint(-getWidth(),0);
1124				textArea.mouseHandler.mouseReleased(e);
1125			}
1126
1127			drag = false;
1128			selectLines = false;
1129		} //}}}
1130	} //}}}
1131}