123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /*
- * Copyright (c) 2006-2017 DMDirc Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
- * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
- * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
- package com.dmdirc.addons.ui_swing.textpane;
-
- import com.dmdirc.ui.messages.CachingDocument;
- import com.dmdirc.ui.messages.LinePosition;
-
- import java.awt.Color;
- import java.awt.Graphics2D;
- import java.awt.Shape;
- import java.awt.font.LineBreakMeasurer;
- import java.awt.font.TextAttribute;
- import java.awt.font.TextLayout;
- import java.awt.geom.Rectangle2D;
- import java.text.AttributedCharacterIterator;
- import java.text.AttributedString;
- import java.util.ArrayList;
- import java.util.List;
-
- import javax.swing.UIManager;
-
- import static com.google.common.base.Preconditions.checkNotNull;
-
- /**
- * Renders basic text, line wrapping where appropriate.
- */
- public class BasicTextLineRenderer implements LineRenderer {
-
- /** Single Side padding for textpane. */
- private static final int SINGLE_SIDE_PADDING = 3;
- /** Both Side padding for textpane. */
- private static final int DOUBLE_SIDE_PADDING = SINGLE_SIDE_PADDING * 2;
-
- /** Render result to use. This instance is recycled for each render call. */
- private final RenderResult result = new RenderResult();
-
- private final TextPane textPane;
- private final TextPaneCanvas textPaneCanvas;
- private final CachingDocument<AttributedString> document;
-
- private final Color highlightForeground;
- private final Color highlightBackground;
-
- /** Reused in each render pass to save creating a new list. */
- private final List<TextLayout> wrappedLines = new ArrayList<>();
-
- public BasicTextLineRenderer(final TextPane textPane, final TextPaneCanvas textPaneCanvas,
- final CachingDocument<AttributedString> document) {
- this.textPane = textPane;
- this.textPaneCanvas = textPaneCanvas;
- this.document = document;
-
- highlightForeground = UIManager.getColor("TextArea.selectionForeground");
- highlightBackground = UIManager.getColor("TextArea.selectionBackground");
- }
-
- @Override
- public RenderResult render(final Graphics2D graphics, final float canvasWidth,
- final float canvasHeight, final float drawPosY, final int line) {
- result.drawnAreas.clear();
- result.textLayouts.clear();
- result.totalHeight = 0;
-
- final AttributedCharacterIterator iterator = document.getStyledLine(line).getIterator();
- final int paragraphStart = iterator.getBeginIndex();
- final int paragraphEnd = iterator.getEndIndex();
- final LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(iterator,
- graphics.getFontRenderContext());
- lineMeasurer.setPosition(paragraphStart);
-
- float newDrawPosY = drawPosY;
-
- // Calculate layouts for each wrapped line.
- wrappedLines.clear();
- int chars = 0;
- while (lineMeasurer.getPosition() < paragraphEnd) {
- final TextLayout layout = checkNotNull(lineMeasurer.nextLayout(canvasWidth));
- chars += layout.getCharacterCount();
- wrappedLines.add(layout);
- }
-
- // Loop through each wrapped line
- for (int i = wrappedLines.size() - 1; i >= 0; i--) {
- final TextLayout layout = wrappedLines.get(i);
-
- // Calculate the initial X position
- final float drawPosX;
- if (layout.isLeftToRight()) {
- drawPosX = SINGLE_SIDE_PADDING;
- } else {
- drawPosX = canvasWidth - layout.getAdvance();
- }
-
- chars -= layout.getCharacterCount();
-
- // Check if the target is in range
- if (newDrawPosY >= 0 || newDrawPosY <= canvasHeight) {
- renderLine(graphics, canvasWidth, line, drawPosX, newDrawPosY, i, chars,
- layout);
- }
-
- // Calculate the Y offset
- newDrawPosY -= layout.getAscent() + layout.getLeading() + layout.getDescent();
- }
-
- result.totalHeight = drawPosY - newDrawPosY;
- return result;
- }
-
- protected void renderLine(final Graphics2D graphics, final float canvasWidth, final int line,
- final float drawPosX, final float drawPosY, final int numberOfWraps, final int chars,
- final TextLayout layout) {
- graphics.setColor(textPane.getForeground());
- layout.draw(graphics, drawPosX, drawPosY);
- doHighlight(line, chars, layout, graphics, canvasWidth + DOUBLE_SIDE_PADDING,
- drawPosX, drawPosY);
- final LineInfo lineInfo = new LineInfo(line, numberOfWraps);
- result.firstVisibleLine = line;
- result.textLayouts.put(lineInfo, layout);
- result.drawnAreas.put(lineInfo,
- new Rectangle2D.Float(0,
- drawPosY - layout.getAscent() - layout.getLeading(),
- canvasWidth + DOUBLE_SIDE_PADDING,
- layout.getAscent() + layout.getDescent() + layout.getLeading()));
- }
-
- /**
- * Redraws the text that has been highlighted.
- *
- * @param line Line number
- * @param chars Number of characters already handled in a wrapped line
- * @param layout Current wrapped line's textlayout
- * @param g Graphics surface to draw highlight on
- * @param drawPosX current x location of the line
- * @param drawPosY current y location of the line
- */
- protected void doHighlight(final int line, final int chars,
- final TextLayout layout, final Graphics2D g, final float canvasWidth,
- final float drawPosX, final float drawPosY) {
- final LinePosition selectedRange = textPaneCanvas.getSelectedRange();
- final int selectionStartLine = selectedRange.getStartLine();
- final int selectionStartChar = selectedRange.getStartPos();
- final int selectionEndLine = selectedRange.getEndLine();
- final int selectionEndChar = selectedRange.getEndPos();
-
- // Does this line need highlighting?
- if (selectionStartLine <= line && selectionEndLine >= line) {
- final int firstChar;
-
- // Determine the first char we care about
- if (selectionStartLine < line || selectionStartChar < chars) {
- firstChar = 0;
- } else {
- firstChar = selectionStartChar - chars;
- }
-
- // ... And the last
- final int lastChar;
- if (selectionEndLine > line || selectionEndChar > chars + layout.getCharacterCount()) {
- lastChar = layout.getCharacterCount();
- } else {
- lastChar = selectionEndChar - chars;
- }
-
- // If the selection includes the chars we're showing
- if (lastChar > 0 && firstChar < layout.getCharacterCount() && lastChar > firstChar) {
- doHighlight(line,
- layout.getLogicalHighlightShape(firstChar, lastChar), g, canvasWidth,
- drawPosY, drawPosX, chars + firstChar, chars + lastChar,
- lastChar == layout.getCharacterCount());
- }
- }
- }
-
- private void doHighlight(final int line, final Shape logicalHighlightShape, final Graphics2D g,
- final float canvasWidth, final float drawPosY, final float drawPosX,
- final int firstChar, final int lastChar, final boolean isEndOfLine) {
- final AttributedCharacterIterator iterator = document.getStyledLine(line).getIterator();
- final AttributedString as = new AttributedString(iterator, firstChar, lastChar);
-
- as.addAttribute(TextAttribute.FOREGROUND, highlightForeground);
- as.addAttribute(TextAttribute.BACKGROUND, highlightBackground);
- final TextLayout newLayout = new TextLayout(as.getIterator(), g.getFontRenderContext());
-
- final Rectangle2D bounds = logicalHighlightShape.getBounds();
-
- g.setColor(highlightBackground);
- g.translate(drawPosX + bounds.getX(), drawPosY);
-
- if (isEndOfLine) {
- g.fill(new Rectangle2D.Double(
- bounds.getWidth(),
- bounds.getY(),
- canvasWidth - bounds.getX() - bounds.getWidth(),
- bounds.getHeight()));
- }
-
- newLayout.draw(g, 0, 0);
- g.translate(-drawPosX - bounds.getX(), -drawPosY);
- }
-
- }
|