PageRenderTime 204ms CodeModel.GetById 162ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/org/gjt/sp/jedit/textarea/TextAreaPainter.java

#
Java | 1257 lines | 710 code | 156 blank | 391 comment | 118 complexity | cabe5074949ddaf73e055d91566fb2c4 MD5 | raw file
   1/*
   2 * TextAreaPainter.java - Paints the text area
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 1999, 2003 Slava Pestov
   7 *
   8 * This program is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU General Public License
  10 * as published by the Free Software Foundation; either version 2
  11 * of the License, or any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  21 */
  22
  23package org.gjt.sp.jedit.textarea;
  24
  25//{{{ Imports
  26import javax.swing.text.*;
  27import javax.swing.JComponent;
  28import java.awt.event.MouseEvent;
  29import java.awt.font.*;
  30import java.awt.*;
  31import java.util.HashMap;
  32import org.gjt.sp.jedit.buffer.IndentFoldHandler;
  33import org.gjt.sp.jedit.syntax.*;
  34import org.gjt.sp.jedit.Buffer;
  35import org.gjt.sp.jedit.Debug;
  36import org.gjt.sp.jedit.OperatingSystem;
  37import org.gjt.sp.util.Log;
  38//}}}
  39
  40/**
  41 * The text area painter is the component responsible for displaying the
  42 * text of the current buffer. The only methods in this class that should
  43 * be called by plugins are those for adding and removing
  44 * text area extensions.
  45 *
  46 * @see #addExtension(TextAreaExtension)
  47 * @see #addExtension(int,TextAreaExtension)
  48 * @see #removeExtension(TextAreaExtension)
  49 * @see TextAreaExtension
  50 * @see JEditTextArea
  51 *
  52 * @author Slava Pestov
  53 * @version $Id: TextAreaPainter.java 4805 2003-06-22 01:09:47Z spestov $
  54 */
  55public class TextAreaPainter extends JComponent implements TabExpander
  56{
  57	//{{{ Layers
  58	/**
  59	 * The lowest possible layer.
  60	 * @see #addExtension(int,TextAreaExtension)
  61	 * @since jEdit 4.0pre4
  62	 */
  63	public static final int LOWEST_LAYER = Integer.MIN_VALUE;
  64
  65	/**
  66	 * Below selection layer. The JDiff plugin will use this.
  67	 * @see #addExtension(int,TextAreaExtension)
  68	 * @since jEdit 4.0pre4
  69	 */
  70	public static final int BACKGROUND_LAYER = -60;
  71
  72	/**
  73	 * The line highlight and collapsed fold highlight layer.
  74	 * @see #addExtension(int,TextAreaExtension)
  75	 * @since jEdit 4.0pre7
  76	 */
  77	public static final int LINE_BACKGROUND_LAYER = -50;
  78
  79	/**
  80	 * Below selection layer.
  81	 * @see #addExtension(int,TextAreaExtension)
  82	 * @since jEdit 4.0pre4
  83	 */
  84	public static final int BELOW_SELECTION_LAYER = -40;
  85
  86	/**
  87	 * Selection layer. Most extensions will be above this layer, but some
  88	 * (eg, JDiff) will want to be below the selection.
  89	 * @see #addExtension(int,TextAreaExtension)
  90	 * @since jEdit 4.0pre4
  91	 */
  92	public static final int SELECTION_LAYER = -30;
  93
  94	/**
  95	 * Wrap guide layer. Most extensions will be above this layer.
  96	 * @since jEdit 4.0pre4
  97	 */
  98	public static final int WRAP_GUIDE_LAYER = -20;
  99
 100	/**
 101	 * Below most extensions layer.
 102	 * @see #addExtension(int,TextAreaExtension)
 103	 * @since jEdit 4.0pre4
 104	 */
 105	public static final int BELOW_MOST_EXTENSIONS_LAYER = -10;
 106
 107	/**
 108	 * Default extension layer. This is above the wrap guide but below the
 109	 * structure highlight.
 110	 * @since jEdit 4.0pre4
 111	 */
 112	public static final int DEFAULT_LAYER = 0;
 113
 114	/**
 115	 * Block caret layer. Most extensions will be below this layer.
 116	 * @since jEdit 4.2pre1
 117	 */
 118	public static final int BLOCK_CARET_LAYER = 50;
 119
 120	/**
 121	 * Bracket highlight layer. Most extensions will be below this layer.
 122	 * @since jEdit 4.0pre4
 123	 */
 124	public static final int BRACKET_HIGHLIGHT_LAYER = 100;
 125
 126	/**
 127	 * Text layer. Most extensions will be below this layer.
 128	 * @since jEdit 4.2pre1
 129	 */
 130	public static final int TEXT_LAYER = 200;
 131
 132	/**
 133	 * Caret layer. Most extensions will be below this layer.
 134	 * @since jEdit 4.2pre1
 135	 */
 136	public static final int CARET_LAYER = 300;
 137
 138	/**
 139	 * Highest possible layer.
 140	 * @since jEdit 4.0pre4
 141	 */
 142	public static final int HIGHEST_LAYER = Integer.MAX_VALUE;
 143	//}}}
 144
 145	//{{{ setBounds() method
 146	/**
 147	 * It is a bad idea to override this, but we need to get the component
 148	 * event before the first repaint.
 149	 */
 150	public void setBounds(int x, int y, int width, int height)
 151	{
 152		if(x == getX() && y == getY() && width == getWidth()
 153			&& height == getHeight())
 154		{
 155			return;
 156		}
 157
 158		super.setBounds(x,y,width,height);
 159
 160		textArea.recalculateVisibleLines();
 161		if(textArea.getBuffer().isLoaded())
 162			textArea.recalculateLastPhysicalLine();
 163		textArea.propertiesChanged();
 164		textArea.scrollBarsInitialized = true;
 165	} //}}}
 166
 167	//{{{ isManagingFocus() method
 168	/**
 169	 * Returns if this component can be traversed by pressing the
 170	 * Tab key. This returns false.
 171	 */
 172	public boolean isManagingFocus()
 173	{
 174		return false;
 175	} //}}}
 176
 177	//{{{ getFocusTraversalKeysEnabled() method
 178	/**
 179	 * Makes the tab key work in Java 1.4.
 180	 * @since jEdit 3.2pre4
 181	 */
 182	public boolean getFocusTraversalKeysEnabled()
 183	{
 184		return false;
 185	} //}}}
 186
 187	//{{{ Getters and setters
 188
 189	//{{{ getStyles() method
 190	/**
 191	 * Returns the syntax styles used to paint colorized text. Entry <i>n</i>
 192	 * will be used to paint tokens with id = <i>n</i>.
 193	 * @see org.gjt.sp.jedit.syntax.Token
 194	 */
 195	public final SyntaxStyle[] getStyles()
 196	{
 197		return styles;
 198	} //}}}
 199
 200	//{{{ setStyles() method
 201	/**
 202	 * Sets the syntax styles used to paint colorized text. Entry <i>n</i>
 203	 * will be used to paint tokens with id = <i>n</i>.
 204	 * @param styles The syntax styles
 205	 * @see org.gjt.sp.jedit.syntax.Token
 206	 */
 207	public final void setStyles(SyntaxStyle[] styles)
 208	{
 209		// assumed this is called after a font render context is set up.
 210		// changing font render context settings without a setStyles()
 211		// call will not reset cached monospaced font info.
 212		fonts.clear();
 213
 214		this.styles = styles;
 215		styles[Token.NULL] = new SyntaxStyle(getForeground(),null,getFont());
 216		for(int i = 0; i < styles.length; i++)
 217		{
 218			styles[i].setCharWidth(getCharWidth(styles[i].getFont()));
 219		}
 220		repaint();
 221	} //}}}
 222
 223	//{{{ getCaretColor() method
 224	/**
 225	 * Returns the caret color.
 226	 */
 227	public final Color getCaretColor()
 228	{
 229		return caretColor;
 230	} //}}}
 231
 232	//{{{ setCaretColor() method
 233	/**
 234	 * Sets the caret color.
 235	 * @param caretColor The caret color
 236	 */
 237	public final void setCaretColor(Color caretColor)
 238	{
 239		this.caretColor = caretColor;
 240		if(textArea.getBuffer() != null)
 241			textArea.invalidateLine(textArea.getCaretLine());
 242	} //}}}
 243
 244	//{{{ getSelectionColor() method
 245	/**
 246	 * Returns the selection color.
 247	 */
 248	public final Color getSelectionColor()
 249	{
 250		return selectionColor;
 251	} //}}}
 252
 253	//{{{ setSelectionColor() method
 254	/**
 255	 * Sets the selection color.
 256	 * @param selectionColor The selection color
 257	 */
 258	public final void setSelectionColor(Color selectionColor)
 259	{
 260		this.selectionColor = selectionColor;
 261		if(textArea.getBuffer() != null)
 262			textArea.invalidateSelectedLines();
 263	} //}}}
 264
 265	//{{{ getMultipleSelectionColor() method
 266	/**
 267	 * Returns the multiple selection color.
 268	 * @since jEdit 4.2pre1
 269	 */
 270	public final Color getMultipleSelectionColor()
 271	{
 272		return multipleSelectionColor;
 273	} //}}}
 274
 275	//{{{ setMultipleSelectionColor() method
 276	/**
 277	 * Sets the multiple selection color.
 278	 * @param multipleSelectionColor The multiple selection color
 279	 * @since jEdit 4.2pre1
 280	 */
 281	public final void setMultipleSelectionColor(Color multipleSelectionColor)
 282	{
 283		this.multipleSelectionColor = multipleSelectionColor;
 284		if(textArea.getBuffer() != null)
 285			textArea.invalidateSelectedLines();
 286	} //}}}
 287
 288	//{{{ getLineHighlightColor() method
 289	/**
 290	 * Returns the line highlight color.
 291	 */
 292	public final Color getLineHighlightColor()
 293	{
 294		return lineHighlightColor;
 295	} //}}}
 296
 297	//{{{ setLineHighlightColor() method
 298	/**
 299	 * Sets the line highlight color.
 300	 * @param lineHighlightColor The line highlight color
 301	 */
 302	public final void setLineHighlightColor(Color lineHighlightColor)
 303	{
 304		this.lineHighlightColor = lineHighlightColor;
 305		if(textArea.getBuffer() != null)
 306			textArea.invalidateLine(textArea.getCaretLine());
 307	} //}}}
 308
 309	//{{{ isLineHighlightEnabled() method
 310	/**
 311	 * Returns true if line highlight is enabled, false otherwise.
 312	 */
 313	public final boolean isLineHighlightEnabled()
 314	{
 315		return lineHighlight;
 316	} //}}}
 317
 318	//{{{ setLineHighlightEnabled() method
 319	/**
 320	 * Enables or disables current line highlighting.
 321	 * @param lineHighlight True if current line highlight should be enabled,
 322	 * false otherwise
 323	 */
 324	public final void setLineHighlightEnabled(boolean lineHighlight)
 325	{
 326		this.lineHighlight = lineHighlight;
 327		if(textArea.getBuffer() != null)
 328			textArea.invalidateSelectedLines();
 329	} //}}}
 330
 331	//{{{ getStructureHighlightColor() method
 332	/**
 333	 * Returns the structure highlight color.
 334	 * @since jEdit 4.2pre3
 335	 */
 336	public final Color getStructureHighlightColor()
 337	{
 338		return structureHighlightColor;
 339	} //}}}
 340
 341	//{{{ setStructureHighlightColor() method
 342	/**
 343	 * Sets the structure highlight color.
 344	 * @param structureHighlightColor The bracket highlight color
 345	 * @since jEdit 4.2pre3
 346	 */
 347	public final void setStructureHighlightColor(
 348		Color structureHighlightColor)
 349	{
 350		this.structureHighlightColor = structureHighlightColor;
 351		StructureMatcher.Match match = textArea.getStructureMatch();
 352		if(match != null)
 353		{
 354			textArea.invalidateLineRange(
 355				match.startLine,match.endLine
 356			);
 357		}
 358	} //}}}
 359
 360	//{{{ isStructureHighlightEnabled() method
 361	/**
 362	 * Returns true if structure highlighting is enabled, false otherwise.
 363	 * @since jEdit 4.2pre3
 364	 */
 365	public final boolean isStructureHighlightEnabled()
 366	{
 367		return structureHighlight;
 368	} //}}}
 369
 370	//{{{ setStructureHighlightEnabled() method
 371	/**
 372	 * Enables or disables structure highlighting.
 373	 * @param structureHighlight True if structure highlighting should be
 374	 * enabled, false otherwise
 375	 * @since jEdit 4.2pre3
 376	 */
 377	public final void setStructureHighlightEnabled(boolean structureHighlight)
 378	{
 379		this.structureHighlight = structureHighlight;
 380		StructureMatcher.Match match = textArea.getStructureMatch();
 381		if(match != null)
 382		{
 383			textArea.invalidateLineRange(
 384				match.startLine,
 385				match.endLine
 386			);
 387		}
 388	} //}}}
 389
 390	//{{{ isBlockCaretEnabled() method
 391	/**
 392	 * Returns true if the caret should be drawn as a block, false otherwise.
 393	 */
 394	public final boolean isBlockCaretEnabled()
 395	{
 396		return blockCaret;
 397	} //}}}
 398
 399	//{{{ setBlockCaretEnabled() method
 400	/**
 401	 * Sets if the caret should be drawn as a block, false otherwise.
 402	 * @param blockCaret True if the caret should be drawn as a block,
 403	 * false otherwise.
 404	 */
 405	public final void setBlockCaretEnabled(boolean blockCaret)
 406	{
 407		this.blockCaret = blockCaret;
 408		extensionMgr.removeExtension(caretExtension);
 409		if(blockCaret)
 410			addExtension(BLOCK_CARET_LAYER,caretExtension);
 411		else
 412			addExtension(CARET_LAYER,caretExtension);
 413		if(textArea.getBuffer() != null)
 414			textArea.invalidateLine(textArea.getCaretLine());
 415	} //}}}
 416
 417	//{{{ getEOLMarkerColor() method
 418	/**
 419	 * Returns the EOL marker color.
 420	 */
 421	public final Color getEOLMarkerColor()
 422	{
 423		return eolMarkerColor;
 424	} //}}}
 425
 426	//{{{ setEOLMarkerColor() method
 427	/**
 428	 * Sets the EOL marker color.
 429	 * @param eolMarkerColor The EOL marker color
 430	 */
 431	public final void setEOLMarkerColor(Color eolMarkerColor)
 432	{
 433		this.eolMarkerColor = eolMarkerColor;
 434		repaint();
 435	} //}}}
 436
 437	//{{{ getEOLMarkersPainted() method
 438	/**
 439	 * Returns true if EOL markers are drawn, false otherwise.
 440	 */
 441	public final boolean getEOLMarkersPainted()
 442	{
 443		return eolMarkers;
 444	} //}}}
 445
 446	//{{{ setEOLMarkersPainted() method
 447	/**
 448	 * Sets if EOL markers are to be drawn.
 449	 * @param eolMarkers True if EOL markers should be drawn, false otherwise
 450	 */
 451	public final void setEOLMarkersPainted(boolean eolMarkers)
 452	{
 453		this.eolMarkers = eolMarkers;
 454		repaint();
 455	} //}}}
 456
 457	//{{{ getWrapGuideColor() method
 458	/**
 459	 * Returns the wrap guide color.
 460	 */
 461	public final Color getWrapGuideColor()
 462	{
 463		return wrapGuideColor;
 464	} //}}}
 465
 466	//{{{ setWrapGuideColor() method
 467	/**
 468	 * Sets the wrap guide color.
 469	 * @param wrapGuideColor The wrap guide color
 470	 */
 471	public final void setWrapGuideColor(Color wrapGuideColor)
 472	{
 473		this.wrapGuideColor = wrapGuideColor;
 474		repaint();
 475	} //}}}
 476
 477	//{{{ isWrapGuidePainted() method
 478	/**
 479	 * Returns true if the wrap guide is drawn, false otherwise.
 480	 * @since jEdit 4.0pre4
 481	 */
 482	public final boolean isWrapGuidePainted()
 483	{
 484		return wrapGuide;
 485	} //}}}
 486
 487	//{{{ setWrapGuidePainted() method
 488	/**
 489	 * Sets if the wrap guide is to be drawn.
 490	 * @param wrapGuide True if the wrap guide should be drawn, false otherwise
 491	 */
 492	public final void setWrapGuidePainted(boolean wrapGuide)
 493	{
 494		this.wrapGuide = wrapGuide;
 495		repaint();
 496	} //}}}
 497
 498	//{{{ getFoldLineStyle() method
 499	/**
 500	 * Returns the fold line style. The first element is the style for
 501	 * lines with a fold level greater than 3. The remaining elements
 502	 * are for fold levels 1 to 3.
 503	 */
 504	public final SyntaxStyle[] getFoldLineStyle()
 505	{
 506		return foldLineStyle;
 507	} //}}}
 508
 509	//{{{ setFoldLineStyle() method
 510	/**
 511	 * Sets the fold line style. The first element is the style for
 512	 * lines with a fold level greater than 3. The remaining elements
 513	 * are for fold levels 1 to 3.
 514	 * @param foldLineStyle The fold line style
 515	 */
 516	public final void setFoldLineStyle(SyntaxStyle[] foldLineStyle)
 517	{
 518		this.foldLineStyle = foldLineStyle;
 519		repaint();
 520	} //}}}
 521
 522	//{{{ setAntiAliasEnabled() method
 523	/**
 524	 * Sets if anti-aliasing should be enabled. Has no effect when
 525	 * running on Java 1.1.
 526	 * @since jEdit 3.2pre6
 527	 */
 528	public void setAntiAliasEnabled(boolean antiAlias)
 529	{
 530		this.antiAlias = antiAlias;
 531		updateRenderingHints();
 532	} //}}}
 533
 534	//{{{ isAntiAliasEnabled() method
 535	/**
 536	 * Returns if anti-aliasing is enabled.
 537	 * @since jEdit 3.2pre6
 538	 */
 539	public boolean isAntiAliasEnabled()
 540	{
 541		return antiAlias;
 542	} //}}}
 543
 544	//{{{ setFractionalFontMetricsEnabled() method
 545	/**
 546	 * Sets if fractional font metrics should be enabled. Has no effect when
 547	 * running on Java 1.1.
 548	 * @since jEdit 3.2pre6
 549	 */
 550	public void setFractionalFontMetricsEnabled(boolean fracFontMetrics)
 551	{
 552		this.fracFontMetrics = fracFontMetrics;
 553		updateRenderingHints();
 554	} //}}}
 555
 556	//{{{ isFractionalFontMetricsEnabled() method
 557	/**
 558	 * Returns if fractional font metrics are enabled.
 559	 * @since jEdit 3.2pre6
 560	 */
 561	public boolean isFractionalFontMetricsEnabled()
 562	{
 563		return fracFontMetrics;
 564	} //}}}
 565
 566	//{{{ getFontRenderContext() method
 567	/**
 568	 * Returns the font render context.
 569	 * @since jEdit 4.0pre4
 570	 */
 571	public FontRenderContext getFontRenderContext()
 572	{
 573		return fontRenderContext;
 574	} //}}}
 575
 576	//}}}
 577
 578	//{{{ addExtension() method
 579	/**
 580	 * Adds a text area extension, which can perform custom painting and
 581	 * tool tip handling.
 582	 * @param extension The extension
 583	 * @since jEdit 4.0pre4
 584	 */
 585	public void addExtension(TextAreaExtension extension)
 586	{
 587		extensionMgr.addExtension(DEFAULT_LAYER,extension);
 588		repaint();
 589	} //}}}
 590
 591	//{{{ addExtension() method
 592	/**
 593	 * Adds a text area extension, which can perform custom painting and
 594	 * tool tip handling.
 595	 * @param layer The layer to add the extension to. Note that more than
 596	 * extension can share the same layer.
 597	 * @param extension The extension
 598	 * @since jEdit 4.0pre4
 599	 */
 600	public void addExtension(int layer, TextAreaExtension extension)
 601	{
 602		extensionMgr.addExtension(layer,extension);
 603		repaint();
 604	} //}}}
 605
 606	//{{{ removeExtension() method
 607	/**
 608	 * Removes a text area extension. It will no longer be asked to
 609	 * perform custom painting and tool tip handling.
 610	 * @param extension The extension
 611	 * @since jEdit 4.0pre4
 612	 */
 613	public void removeExtension(TextAreaExtension extension)
 614	{
 615		extensionMgr.removeExtension(extension);
 616		repaint();
 617	} //}}}
 618
 619	//{{{ getExtensions() method
 620	/**
 621	 * Returns an array of registered text area extensions. Useful for
 622	 * debugging purposes.
 623	 * @since jEdit 4.1pre5
 624	 */
 625	public TextAreaExtension[] getExtensions()
 626	{
 627		return extensionMgr.getExtensions();
 628	} //}}}
 629
 630	//{{{ getToolTipText() method
 631	/**
 632	 * Returns the tool tip to display at the specified location.
 633	 * @param evt The mouse event
 634	 */
 635	public String getToolTipText(MouseEvent evt)
 636	{
 637		if(!textArea.getBuffer().isLoaded())
 638			return null;
 639
 640		return extensionMgr.getToolTipText(evt.getX(),evt.getY());
 641	} //}}}
 642
 643	//{{{ getFontMetrics() method
 644	/**
 645	 * Returns the font metrics used by this component.
 646	 */
 647	public FontMetrics getFontMetrics()
 648	{
 649		return fm;
 650	} //}}}
 651
 652	//{{{ setFont() method
 653	/**
 654	 * Sets the font for this component. This is overridden to update the
 655	 * cached font metrics and to recalculate which lines are visible.
 656	 * @param font The font
 657	 */
 658	public void setFont(Font font)
 659	{
 660		super.setFont(font);
 661		fm = getFontMetrics(font);
 662		textArea.recalculateVisibleLines();
 663		textArea.propertiesChanged();
 664	} //}}}
 665
 666	//{{{ paintComponent() method
 667	/**
 668	 * Repaints the text.
 669	 * @param g The graphics context
 670	 */
 671	public void paintComponent(Graphics _gfx)
 672	{
 673		Graphics2D gfx = (Graphics2D)_gfx;
 674		gfx.setRenderingHints(renderingHints);
 675		fontRenderContext = gfx.getFontRenderContext();
 676
 677		Rectangle clipRect = gfx.getClipBounds();
 678
 679		gfx.setColor(getBackground());
 680		gfx.fillRect(clipRect.x,clipRect.y,clipRect.width,clipRect.height);
 681
 682		Buffer buffer = textArea.getBuffer();
 683		if(!buffer.isLoaded())
 684			return;
 685
 686		int x = textArea.getHorizontalOffset();
 687
 688		int height = fm.getHeight();
 689		int firstInvalid = clipRect.y / height;
 690		// Because the clipRect's height is usually an even multiple
 691		// of the font height, we subtract 1 from it, otherwise one
 692		// too many lines will always be painted.
 693		int lastInvalid = (clipRect.y + clipRect.height - 1) / height;
 694
 695		if(Debug.PAINT_TIMER && lastInvalid - firstInvalid >= 1)
 696			Log.log(Log.DEBUG,this,"repainting " + (lastInvalid - firstInvalid) + " lines");
 697
 698		int y = (clipRect.y - clipRect.y % height);
 699
 700		textArea.updateMaxHorizontalScrollWidth = false;
 701
 702		extensionMgr.paintScreenLineRange(textArea,gfx,
 703			firstInvalid,lastInvalid,y,height);
 704
 705		if(textArea.updateMaxHorizontalScrollWidth)
 706			textArea.updateMaxHorizontalScrollWidth();
 707
 708		if(buffer.isNextLineRequested())
 709		{
 710			int h = clipRect.y + clipRect.height;
 711			textArea.chunkCache.invalidateChunksFrom(lastInvalid + 1);
 712			repaint(0,h,getWidth(),getHeight() - h);
 713		}
 714
 715		textArea.displayManager._notifyScreenLineChanges();
 716	} //}}}
 717
 718	//{{{ nextTabStop() method
 719	/**
 720	 * Implementation of TabExpander interface. Returns next tab stop after
 721	 * a specified point.
 722	 * @param x The x co-ordinate
 723	 * @param tabOffset Ignored
 724	 * @return The next tab stop after <i>x</i>
 725	 */
 726	public float nextTabStop(float x, int tabOffset)
 727	{
 728		int ntabs = (int)(x / textArea.tabSize);
 729		return (ntabs + 1) * textArea.tabSize;
 730	} //}}}
 731
 732	//{{{ getPreferredSize() method
 733	/**
 734	 * Returns the painter's preferred size.
 735	 */
 736	public Dimension getPreferredSize()
 737	{
 738		Dimension dim = new Dimension();
 739
 740		char[] foo = new char[80];
 741		for(int i = 0; i < foo.length; i++)
 742			foo[i] = ' ';
 743		dim.width = (int)(getFont().getStringBounds(foo,0,foo.length,
 744			fontRenderContext).getWidth());
 745		dim.height = fm.getHeight() * 25;
 746		return dim;
 747	} //}}}
 748
 749	//{{{ getMinimumSize() method
 750	/**
 751	 * Returns the painter's minimum size.
 752	 */
 753	public Dimension getMinimumSize()
 754	{
 755		return getPreferredSize();
 756	} //}}}
 757
 758	//{{{ Package-private members
 759
 760	//{{{ Instance variables
 761	/* package-private since they are accessed by inner classes and we
 762	 * want this to be fast */
 763	JEditTextArea textArea;
 764
 765	SyntaxStyle[] styles;
 766	Color caretColor;
 767	Color selectionColor;
 768	Color multipleSelectionColor;
 769	Color lineHighlightColor;
 770	Color structureHighlightColor;
 771	Color eolMarkerColor;
 772	Color wrapGuideColor;
 773
 774	SyntaxStyle[] foldLineStyle;
 775
 776	boolean blockCaret;
 777	boolean lineHighlight;
 778	boolean structureHighlight;
 779	boolean eolMarkers;
 780	boolean wrapGuide;
 781	boolean antiAlias;
 782	boolean fracFontMetrics;
 783
 784	// should try to use this as little as possible.
 785	FontMetrics fm;
 786	//}}}
 787
 788	//{{{ TextAreaPainter constructor
 789	/**
 790	 * Creates a new painter. Do not create instances of this class
 791	 * directly.
 792	 */
 793	TextAreaPainter(JEditTextArea textArea)
 794	{
 795		enableEvents(AWTEvent.FOCUS_EVENT_MASK
 796			| AWTEvent.KEY_EVENT_MASK
 797			| AWTEvent.MOUSE_EVENT_MASK);
 798
 799		this.textArea = textArea;
 800
 801		fonts = new HashMap();
 802		extensionMgr = new ExtensionManager();
 803
 804		setAutoscrolls(true);
 805		setOpaque(true);
 806		setRequestFocusEnabled(false);
 807
 808		setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
 809
 810		fontRenderContext = new FontRenderContext(null,false,false);
 811
 812		addExtension(LINE_BACKGROUND_LAYER,new PaintLineBackground());
 813		addExtension(SELECTION_LAYER,new PaintSelection());
 814		addExtension(WRAP_GUIDE_LAYER,new PaintWrapGuide());
 815		addExtension(BRACKET_HIGHLIGHT_LAYER,new StructureMatcher
 816			.Highlight(textArea));
 817		addExtension(TEXT_LAYER,new PaintText());
 818		caretExtension = new PaintCaret();
 819	} //}}}
 820
 821	//}}}
 822
 823	//{{{ Private members
 824
 825	//{{{ Instance variables
 826	private ExtensionManager extensionMgr;
 827	private PaintCaret caretExtension;
 828	private RenderingHints renderingHints;
 829	private FontRenderContext fontRenderContext;
 830	private HashMap fonts;
 831	//}}}
 832
 833	//{{{ updateRenderingHints() method
 834	private void updateRenderingHints()
 835	{
 836		HashMap hints = new HashMap();
 837
 838		if(antiAlias)
 839		{
 840			//hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
 841			hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 842			hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 843		}
 844		else
 845		{
 846			hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
 847			hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
 848		}
 849
 850		hints.put(RenderingHints.KEY_FRACTIONALMETRICS,
 851			fracFontMetrics ?
 852				RenderingHints.VALUE_FRACTIONALMETRICS_ON
 853				: RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
 854
 855		renderingHints = new RenderingHints(hints);
 856		fontRenderContext = new FontRenderContext(null,antiAlias,
 857			fracFontMetrics);
 858	} //}}}
 859
 860	//{{{ getCharWidth() method
 861	private int getCharWidth(Font font)
 862	{
 863		Integer returnValue = (Integer)fonts.get(font);
 864		if(returnValue == null)
 865		{
 866			int minWidth = Integer.MAX_VALUE;
 867			int maxWidth = Integer.MIN_VALUE;
 868			FontMetrics fm = getFontMetrics(font);
 869			int[] widths = fm.getWidths();
 870			for(int i = 0; i < widths.length; i++)
 871			{
 872				int width = widths[i];
 873				if(width == 0 || !font.canDisplay((char)i))
 874					continue;
 875				minWidth = Math.min(width,minWidth);
 876				maxWidth = Math.max(width,maxWidth);
 877			}
 878
 879			String str = "iwiwiwiau1234";
 880			double width1 = font.createGlyphVector(textArea.getPainter()
 881				.getFontRenderContext(),str).getLogicalBounds()
 882				.getWidth();
 883			double width2 = str.length() * maxWidth;
 884			if(minWidth == maxWidth
 885				&& width1 == width2)
 886			{
 887				Log.log(Log.DEBUG,this,"Using monospaced font optimization: " + font);
 888				returnValue = new Integer(maxWidth);
 889			}
 890			else
 891			{
 892				Log.log(Log.DEBUG,this,"Not using monospaced font optimization: " + font);
 893				Log.log(Log.DEBUG,this,"Minimum width = " + minWidth
 894					+ ", maximum width = " + maxWidth);
 895				returnValue = new Integer(0);
 896			}
 897
 898			fonts.put(font,returnValue);
 899		}
 900		return returnValue.intValue();
 901	} //}}}
 902
 903	//}}}
 904
 905	//{{{ Inner classes
 906
 907	//{{{ PaintLineBackground class
 908	class PaintLineBackground extends TextAreaExtension
 909	{
 910		//{{{ paintValidLine() method
 911		public void paintValidLine(Graphics2D gfx, int screenLine,
 912			int physicalLine, int start, int end, int y)
 913		{
 914			// minimise access$ methods
 915			JEditTextArea textArea = TextAreaPainter.this.textArea;
 916			Buffer buffer = textArea.getBuffer();
 917
 918			//{{{ Paint line highlight and collapsed fold highlight
 919			boolean collapsedFold =
 920				(physicalLine < buffer.getLineCount() - 1
 921				&& buffer.isFoldStart(physicalLine)
 922				&& !textArea.displayManager
 923				.isLineVisible(physicalLine + 1));
 924
 925			SyntaxStyle foldLineStyle = null;
 926			if(collapsedFold)
 927			{
 928				int level = buffer.getFoldLevel(physicalLine + 1);
 929				if(buffer.getFoldHandler() instanceof IndentFoldHandler)
 930					level = Math.max(1,level / buffer.getIndentSize());
 931				if(level > 3)
 932					level = 0;
 933				foldLineStyle = TextAreaPainter.this.foldLineStyle[level];
 934			}
 935
 936			int caret = textArea.getCaretPosition();
 937			boolean paintLineHighlight = isLineHighlightEnabled()
 938				&& caret >= start && caret < end
 939				&& textArea.selection.size() == 0;
 940
 941			Color bgColor;
 942			if(paintLineHighlight)
 943				bgColor = lineHighlightColor;
 944			else if(collapsedFold)
 945			{
 946				bgColor = foldLineStyle.getBackgroundColor();
 947				if(bgColor == null)
 948					bgColor = getBackground();
 949			}
 950			else
 951				bgColor = getBackground();
 952
 953			if(paintLineHighlight || collapsedFold)
 954			{
 955				gfx.setColor(bgColor);
 956				gfx.fillRect(0,y,getWidth(),fm.getHeight());
 957			} //}}}
 958
 959			//{{{ Paint token backgrounds
 960			ChunkCache.LineInfo lineInfo = textArea.chunkCache
 961				.getLineInfo(screenLine);
 962
 963			if(lineInfo.chunks != null)
 964			{
 965				float baseLine = y + fm.getHeight()
 966					- fm.getLeading() - fm.getDescent();
 967				Chunk.paintChunkBackgrounds(
 968					lineInfo.chunks,gfx,
 969					textArea.getHorizontalOffset(),
 970					baseLine);
 971			} //}}}
 972		} //}}}
 973	} //}}}
 974
 975	//{{{ PaintSelection class
 976	class PaintSelection extends TextAreaExtension
 977	{
 978		//{{{ paintValidLine() method
 979		public void paintValidLine(Graphics2D gfx, int screenLine,
 980			int physicalLine, int start, int end, int y)
 981		{
 982			if(textArea.selection.size() == 0)
 983				return;
 984
 985			gfx.setColor(textArea.isMultipleSelectionEnabled()
 986				? getMultipleSelectionColor()
 987				: getSelectionColor());
 988			for(int i = textArea.selection.size() - 1;
 989				i >= 0; i--)
 990			{
 991				paintSelection(gfx,screenLine,
 992					physicalLine,start,end,y,
 993					(Selection)textArea.selection
 994					.get(i));
 995			}
 996		} //}}}
 997
 998		//{{{ paintSelection() method
 999		private void paintSelection(Graphics2D gfx, int screenLine,
1000			int physicalLine, int start, int end, int y, Selection s)
1001		{
1002			if(end <= s.start || start > s.end)
1003				return;
1004
1005			int selStartScreenLine = textArea.getScreenLineOfOffset(s.start);
1006			int selEndScreenLine = textArea.getScreenLineOfOffset(s.end);
1007
1008			Buffer buffer = textArea.getBuffer();
1009
1010			int lineStart = buffer.getLineStartOffset(physicalLine);
1011
1012			int x1, x2;
1013
1014			if(s instanceof Selection.Rect)
1015			{
1016				start -= lineStart;
1017				end -= lineStart;
1018
1019				Selection.Rect rect = (Selection.Rect)s;
1020				int _start = rect.getStartColumn(buffer);
1021				int _end = rect.getEndColumn(buffer);
1022
1023				int lineLen = buffer.getLineLength(physicalLine);
1024
1025				int[] total = new int[1];
1026
1027				int rectStart = buffer.getOffsetOfVirtualColumn(
1028					physicalLine,_start,total);
1029				if(rectStart == -1)
1030				{
1031					x1 = (_start - total[0]) * textArea.charWidth;
1032					rectStart = lineLen;
1033				}
1034				else
1035					x1 = 0;
1036
1037				int rectEnd = buffer.getOffsetOfVirtualColumn(
1038					physicalLine,_end,total);
1039				if(rectEnd == -1)
1040				{
1041					x2 = (_end - total[0]) * textArea.charWidth;
1042					rectEnd = lineLen;
1043				}
1044				else
1045					x2 = 0;
1046
1047				if(end <= rectStart || start > rectEnd)
1048					return;
1049
1050				x1 = (rectStart < start ? 0
1051					: x1 + textArea.offsetToXY(physicalLine,rectStart,textArea.returnValue).x);
1052				x2 = (rectEnd > end ? getWidth()
1053					: x2 + textArea.offsetToXY(physicalLine,rectEnd,textArea.returnValue).x);
1054			}
1055			else if(selStartScreenLine == selEndScreenLine
1056				&& selStartScreenLine != -1)
1057			{
1058				x1 = textArea.offsetToXY(physicalLine,
1059					s.start - lineStart,textArea.returnValue).x;
1060				x2 = textArea.offsetToXY(physicalLine,
1061					s.end - lineStart,textArea.returnValue).x;
1062			}
1063			else if(screenLine == selStartScreenLine)
1064			{
1065				x1 = textArea.offsetToXY(physicalLine,
1066					s.start - lineStart,textArea.returnValue).x;
1067				x2 = getWidth();
1068			}
1069			else if(screenLine == selEndScreenLine)
1070			{
1071				x1 = 0;
1072				x2 = textArea.offsetToXY(physicalLine,
1073					s.end - lineStart,textArea.returnValue).x;
1074			}
1075			else
1076			{
1077				x1 = 0;
1078				x2 = getWidth();
1079			}
1080
1081			if(x1 < 0)
1082				x1 = 0;
1083			if(x2 < 0)
1084				x2 = 0;
1085
1086			if(x1 == x2)
1087				x2++;
1088
1089			gfx.fillRect(x1,y,x2 - x1,fm.getHeight());
1090		} //}}}
1091	} //}}}
1092
1093	//{{{ PaintWrapGuide class
1094	class PaintWrapGuide extends TextAreaExtension
1095	{
1096		public void paintScreenLineRange(Graphics2D gfx, int firstLine,
1097			int lastLine, int[] physicalLines, int[] start,
1098			int[] end, int y, int lineHeight)
1099		{
1100			if(textArea.getDisplayManager().wrapMargin != 0
1101				&& isWrapGuidePainted())
1102			{
1103				gfx.setColor(getWrapGuideColor());
1104				int x = textArea.getHorizontalOffset()
1105					+ textArea.getDisplayManager()
1106					.wrapMargin;
1107				gfx.drawLine(x,y,x,y + (lastLine - firstLine
1108					+ 1) * lineHeight);
1109			}
1110		}
1111
1112		public String getToolTipText(int x, int y)
1113		{
1114			if(textArea.getDisplayManager().wrapMargin != 0 && isWrapGuidePainted())
1115			{
1116				int wrapGuidePos = textArea.getDisplayManager().wrapMargin
1117					+ textArea.getHorizontalOffset();
1118				if(Math.abs(x - wrapGuidePos) < 5)
1119				{
1120					return String.valueOf(textArea.getBuffer()
1121						.getProperty("maxLineLen"));
1122				}
1123			}
1124
1125			return null;
1126		}
1127	} //}}}
1128
1129	//{{{ PaintText class
1130	class PaintText extends TextAreaExtension
1131	{
1132		public void paintValidLine(Graphics2D gfx, int screenLine,
1133			int physicalLine, int start, int end, int y)
1134		{
1135			ChunkCache.LineInfo lineInfo = textArea.chunkCache
1136				.getLineInfo(screenLine);
1137
1138			Font defaultFont = getFont();
1139			Color defaultColor = getForeground();
1140
1141			gfx.setFont(defaultFont);
1142			gfx.setColor(defaultColor);
1143
1144			int x = textArea.getHorizontalOffset();
1145			int originalX = x;
1146
1147			float baseLine = y + fm.getHeight()
1148				- fm.getLeading() - fm.getDescent();
1149
1150			if(lineInfo.chunks != null)
1151			{
1152				x += Chunk.paintChunkList(lineInfo.chunks,
1153					gfx,textArea.getHorizontalOffset(),
1154					baseLine,!Debug.DISABLE_GLYPH_VECTOR);
1155			}
1156
1157			Buffer buffer = textArea.getBuffer();
1158
1159			if(!lineInfo.lastSubregion)
1160			{
1161				gfx.setFont(defaultFont);
1162				gfx.setColor(eolMarkerColor);
1163				gfx.drawString(":",Math.max(x,
1164					textArea.getHorizontalOffset()
1165					+ textArea.getDisplayManager().wrapMargin + textArea.charWidth),
1166					baseLine);
1167				x += textArea.charWidth;
1168			}
1169			else if(physicalLine < buffer.getLineCount() - 1
1170				&& buffer.isFoldStart(physicalLine)
1171				&& !textArea.displayManager
1172				.isLineVisible(physicalLine + 1))
1173			{
1174				int level = buffer.getFoldLevel(physicalLine + 1);
1175				if(buffer.getFoldHandler() instanceof IndentFoldHandler)
1176					level = Math.max(1,level / buffer.getIndentSize());
1177				if(level > 3)
1178					level = 0;
1179				SyntaxStyle foldLineStyle = TextAreaPainter.this.foldLineStyle[level];
1180
1181				Font font = foldLineStyle.getFont();
1182				gfx.setFont(font);
1183				gfx.setColor(foldLineStyle.getForegroundColor());
1184
1185				int nextLine;
1186				int nextScreenLine = screenLine + 1;
1187				if(nextScreenLine < textArea.getVisibleLines())
1188				{
1189					nextLine = textArea.chunkCache.getLineInfo(nextScreenLine)
1190						.physicalLine;
1191				}
1192				else
1193				{
1194					nextLine = textArea.displayManager
1195						.getNextVisibleLine(physicalLine);
1196				}
1197
1198				if(nextLine == -1)
1199					nextLine = textArea.getLineCount();
1200
1201				int count = nextLine - physicalLine - 1;
1202				String str = " [" + count + " lines]";
1203
1204				float width = (float)font.getStringBounds(
1205					str,fontRenderContext).getWidth();
1206
1207				gfx.drawString(str,x,baseLine);
1208				x += width;
1209			}
1210			else if(eolMarkers)
1211			{
1212				gfx.setFont(defaultFont);
1213				gfx.setColor(eolMarkerColor);
1214				gfx.drawString(".",x,baseLine);
1215				x += textArea.charWidth;
1216			}
1217
1218			lineInfo.width = (x - originalX);
1219			if(lineInfo.width > textArea.maxHorizontalScrollWidth)
1220				textArea.updateMaxHorizontalScrollWidth = true;
1221		}
1222	} //}}}
1223
1224	//{{{ PaintCaret class
1225	class PaintCaret extends TextAreaExtension
1226	{
1227		public void paintValidLine(Graphics2D gfx, int screenLine,
1228			int physicalLine, int start, int end, int y)
1229		{
1230			if(!textArea.isCaretVisible())
1231				return;
1232
1233			int caret = textArea.getCaretPosition();
1234			if(caret < start || caret >= end)
1235				return;
1236
1237			int offset = caret - textArea.getLineStartOffset(physicalLine);
1238			textArea.offsetToXY(physicalLine,offset,textArea.returnValue);
1239			int caretX = textArea.returnValue.x;
1240			int height = fm.getHeight();
1241
1242			gfx.setColor(caretColor);
1243
1244			if(textArea.isOverwriteEnabled())
1245			{
1246				gfx.drawLine(caretX,y + height - 1,
1247					caretX + textArea.charWidth,y + height - 1);
1248			}
1249			else if(blockCaret)
1250				gfx.drawRect(caretX,y,textArea.charWidth - 1,height - 1);
1251			else
1252				gfx.drawLine(caretX,y,caretX,y + height - 1);
1253		}
1254	} //}}}
1255
1256	//}}}
1257}