package org.basex.gui.layout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Point; import java.util.Locale; import org.basex.gui.GUIConstants; import org.basex.gui.GUIConstants.Fill; import org.basex.util.TokenBuilder; /** * Efficient Text Editor and Renderer, supporting syntax highlighting and * text selections. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ final class BaseXTextRenderer extends BaseXBack { /** Vertical start position. */ private final BaseXBar bar; /** Font. */ private Font font; /** Default font. */ private Font dfont; /** Bold font. */ private Font bfont; /** Font height. */ private int fontH; /** Character widths. */ private int[] fwidth = GUIConstants.mfwidth; /** Color. */ private Color color; /** Color highlighting flag. */ private boolean high; /** Width of current word. */ private int wordW; /** Search term. */ private String search; /** Border offset. */ private int off; /** Current x coordinate. */ private int x; /** Current y coordinate. */ private int y; /** Current width. */ private int w; /** Current height. */ private int h; /** Text array to be written. */ private transient BaseXTextTokens text; /** Vertical start position. */ private transient BaseXSyntax syntax = BaseXSyntax.SIMPLE; /** Visibility of cursor. */ private boolean cursor; /** * Constructor. * @param t text to be drawn * @param b scrollbar reference */ BaseXTextRenderer(final BaseXTextTokens t, final BaseXBar b) { mode(Fill.NONE).setFont(GUIConstants.dfont); text = t; bar = b; } @Override public void setFont(final Font f) { dfont = f; bfont = f.deriveFont(Font.BOLD); font(f); } @Override public void paintComponent(final Graphics g) { super.paintComponent(g); write(g, bar.pos()); } /** * Initializes the constructor. * @param t text to be drawn */ void setText(final BaseXTextTokens t) { text = t; } /** * Finds the search term. * @param s search term * @param b backward browsing * @return new position */ int find(final String s, final boolean b) { final int os = search == null ? 0 : search.length(); final int ns = s.length(); search = ns != 0 ? s.toLowerCase(Locale.ENGLISH) : null; return ns < os ? 0 : find(b, ns == os); } /** * Finds the search term. * @param b backward browsing * @param s string is the same as last time * @return new position */ int find(final boolean b, final boolean s) { if(search == null) return 0; final int hh = h; int lp = 0; int ly = 0; int sp = text.cursor(); h = Integer.MAX_VALUE; final Graphics g = getGraphics(); init(g, 0); while(more(g)) { if(searched()) { final int np = text.pos(); final int ny = y - fontH; if(np >= sp && (np > sp || !s || b)) { if(b && lp == 0 && np >= sp) { sp = Integer.MAX_VALUE; } else { h = hh; text.setCaret(b ? lp : np); return b ? ly : ny; } } lp = np; ly = ny; } next(); } h = hh; if(sp == 0 || sp == Integer.MAX_VALUE) { text.setCaret(lp); return ly; } text.setCaret(0); return find(b, s); } /** * Returns the cursor coordinates. * @return line/column */ int[] pos() { final int hh = h; h = Integer.MAX_VALUE; final Graphics g = getGraphics(); int col = 1; int line = 1; init(g, 0); boolean more = true; while(more(g)) { final int p = text.pos(); while(text.more()) { more = text.pos() < text.cursor(); if(!more) break; text.next(); col++; } if(!more) break; text.pos(p); if(next()) { line++; col = 1; } } h = hh; return new int[] { line, col }; } /** * Sets the current font. * @param f font */ private void font(final Font f) { font = f; off = f.getSize() + 1 >> 2; fontH = f.getSize() + off; fwidth = GUIConstants.fontWidths(f); } @Override public Dimension getPreferredSize() { final Graphics g = getGraphics(); w = Integer.MAX_VALUE; h = Integer.MAX_VALUE; init(g, 0); int max = 0; while(more(g)) { if(text.curr() == 0x0A) max = Math.max(x, max); next(); } return new Dimension(Math.max(x, max) + fwidth[' '], y + fontH); } /** * Initializes the renderer. * @param g graphics reference * @param pos current text position */ private void init(final Graphics g, final int pos) { font = dfont; color = Color.black; syntax.init(); text.init(); x = off; y = off + fontH - pos - 2; if(g != null) g.setFont(font); } /** * Calculates the text height. */ void calc() { w = getWidth() - (off >> 1); h = Integer.MAX_VALUE; final Graphics g = getGraphics(); init(g, 0); while(more(g)) next(); h = getHeight() + fontH; bar.height(y + off); } /** * Returns the current vertical cursor position. * @return new position */ int cursorY() { final int hh = h; h = Integer.MAX_VALUE; final Graphics g = getGraphics(); init(g, 0); while(more(g) && !text.edited()) next(); h = hh; return y - fontH; } /** * Writes the text. * @param g graphics reference * @param pos current text position */ private void write(final Graphics g, final int pos) { init(g, pos); while(more(g)) write(g); if(cursor && text.cursor() == text.size()) cursor(g, x); } /** * Checks if the text has more words to print. * @param g graphics reference * @return true if the text has more words */ private boolean more(final Graphics g) { // no more words found; quit if(!text.moreWords()) return false; // calculate word width int ww = 0; final int p = text.pos(); while(text.more()) { final int ch = text.next(); // internal special codes... if(ch == 0x02) { font(bfont); } else if(ch == 0x03) { font(dfont); } else { ww += charW(g, ch); } } text.pos(p); // jump to new line if(x + ww > w) { x = off; y += fontH; } wordW = ww; // check if word has been found, and word is still visible return y < h; } /** * Finishes the current token. * @return true for new line */ private boolean next() { final int ch = text.curr(); if(ch == TokenBuilder.NLINE || ch == TokenBuilder.HLINE) { x = off; y += fontH >> (ch == TokenBuilder.NLINE ? 0 : 1); return true; } x += wordW; return false; } /** * Writes the current string to the graphics reference. * @param g graphics reference */ private void write(final Graphics g) { if(high) { high = false; } else { color = isEnabled() ? syntax.getColor(text) : Color.gray; } final int ch = text.curr(); if(y > 0 && y < h) { if(ch == TokenBuilder.MARK) { color = GUIConstants.GREEN; high = true; } // mark error if(text.error()) { g.setColor(GUIConstants.LRED); g.fillRect(x, y - fontH + 4, wordW, fontH); } // mark text int xx = x; if(text.markStart()) { final int p = text.pos(); while(text.more()) { final int cw = charW(g, text.curr()); if(text.inMark()) { g.setColor(GUIConstants.color(3)); g.fillRect(xx, y - fontH + 4, cw, fontH); } xx += cw; text.next(); } text.pos(p); } if(search != null && searched()) { int cw = 0; for(int c = 0; c < search.length(); ++c) { cw += charW(g, search.charAt(c)); } g.setColor(GUIConstants.color(text.cursor() == text.pos() ? 5 : 2)); g.fillRect(x, y - fontH + 4, cw, fontH); } // don't write whitespaces if(ch > ' ') { g.setColor(color); g.drawString(text.nextWord(), x, y); } else if(ch <= TokenBuilder.MARK) { g.setFont(font); } // show cursor if(cursor && text.edited()) { xx = x; final int p = text.pos(); while(text.more()) { if(text.cursor() == text.pos()) { cursor(g, xx); break; } xx += charW(g, text.next()); } text.pos(p); } } next(); } /** * Returns true if the searched term is found. * @return result of check */ private boolean searched() { final int sl = search.length(); final int wl = text.length(); if(wl < sl) return false; final int p = text.pos(); int s = -1; while(++s != sl) { if(Character.toLowerCase(text.next()) != search.charAt(s)) break; } text.pos(p); return s == sl; } /** * Paints the text cursor. * @param g graphics reference * @param xx x position */ private void cursor(final Graphics g, final int xx) { g.setColor(Color.black); g.drawLine(xx, y - fontH + 4, xx, y + 3); } /** * Finishes the selection. */ void stopSelect() { text.checkMark(); } /** * Selects the text at the specified position. * @param pos current text position * @param p mouse position * @param finish states if selection is in progress */ void select(final int pos, final Point p, final boolean finish) { if(!finish) text.noMark(); p.y -= 3; final Graphics g = getGraphics(); init(g, pos); if(p.y > y - fontH) { int s = text.pos(); while(true) { // end of line if(p.x > x && p.y < y - fontH) { text.pos(s); break; } // end of text - skip last characters if(!more(g)) { while(text.more()) text.next(); break; } // beginning of line if(p.x <= x && p.y < y) break; // middle of line if(p.x > x && p.x <= x + wordW && p.y > y - fontH && p.y <= y) { while(text.more()) { final int ww = charW(g, text.curr()); if(p.x < x + ww) break; x += ww; text.next(); } break; } s = text.pos(); next(); } if(!finish) text.startMark(); else text.endMark(); text.setCaret(); } repaint(); } /** * Returns the width of the specified codepoint. * @param g graphics reference * @param cp character * @return width */ private int charW(final Graphics g, final int cp) { return cp < ' ' || g == null ? cp == '\t' ? fwidth[' '] * BaseXTextTokens.TAB : 0 : cp < 256 ? fwidth[cp] : cp >= 0xD800 && cp <= 0xDC00 ? 0 : g.getFontMetrics().charWidth(cp); } /** * Returns the font height. * @return font height */ int fontH() { return fontH; } /** * Sets the cursor flag. * @param c cursor flag */ void cursor(final boolean c) { cursor = c; } /** * Returns the cursor flag. * @return cursor flag */ boolean cursor() { return cursor; } /** * Sets a syntax highlighter. * @param s syntax highlighter */ void setSyntax(final BaseXSyntax s) { syntax = s; } /** * Returns the syntax highlighter. * @return syntax highlighter */ BaseXSyntax getSyntax() { return syntax; } }