/** * Provides a way to determine the next visually represented model location at which one might * place a caret. Some views may not be visible, they might not be in the same order found in the * model, or they just might not allow access to some of the locations in the model. * * @param pos the position to convert >= 0 * @param a the allocated region in which to render * @param direction the direction from the current position that can be thought of as the arrow * keys typically found on a keyboard. This will be one of the following values: * <ul> * <li>SwingConstants.WEST * <li>SwingConstants.EAST * <li>SwingConstants.NORTH * <li>SwingConstants.SOUTH * </ul> * * @return the location within the model that best represents the next location visual position * @exception BadLocationException * @exception IllegalArgumentException if <code>direction</code> doesn't have one of the legal * values above */ public int getNextVisualPositionFrom( int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) throws BadLocationException { biasRet[0] = Position.Bias.Forward; switch (direction) { case NORTH: case SOUTH: { if (pos == -1) { pos = (direction == NORTH) ? Math.max(0, getEndOffset() - 1) : getStartOffset(); break; } JTextComponent target = (JTextComponent) getContainer(); Caret c = (target != null) ? target.getCaret() : null; // YECK! Ideally, the x location from the magic caret position // would be passed in. Point mcp; if (c != null) { mcp = c.getMagicCaretPosition(); } else { mcp = null; } int x; if (mcp == null) { Rectangle loc = target.modelToView(pos); x = (loc == null) ? 0 : loc.x; } else { x = mcp.x; } if (direction == NORTH) { pos = Utilities.getPositionAbove(target, pos, x); } else { pos = Utilities.getPositionBelow(target, pos, x); } } break; case WEST: if (pos == -1) { pos = Math.max(0, getEndOffset() - 1); } else { pos = Math.max(0, pos - 1); } break; case EAST: if (pos == -1) { pos = getStartOffset(); } else { pos = Math.min(pos + 1, getDocument().getLength()); } break; default: throw new IllegalArgumentException("Bad direction: " + direction); } return pos; }
/* * Determine the Y offset for the current row */ private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException { // Get the bounding rectangle of the row Rectangle r = component.modelToView(rowStartOffset); int lineHeight = fontMetrics.getHeight(); int y = r.y + r.height; int descent = 0; // The text needs to be positioned above the bottom of the bounding // rectangle based on the descent of the font(s) contained on the row. if (r.height == lineHeight) { // default font is being used descent = fontMetrics.getDescent(); } else { // We need to check all the attributes for font changes if (fonts == null) { fonts = new HashMap<String, FontMetrics>(); } Element root = component.getDocument().getDefaultRootElement(); int index = root.getElementIndex(rowStartOffset); Element line = root.getElement(index); for (int i = 0; i < line.getElementCount(); i++) { Element child = line.getElement(i); AttributeSet as = child.getAttributes(); String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily); Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize); String key = fontFamily + fontSize; FontMetrics fm = fonts.get(key); if (fm == null) { Font font = new Font(fontFamily, Font.PLAIN, fontSize); fm = component.getFontMetrics(font); fonts.put(key, fm); } descent = Math.max(descent, fm.getDescent()); } } return y - descent; }
/* * A document change may affect the number of displayed lines of text. * Therefore the lines numbers will also change. */ private void documentChanged() { // View of the component has not been updated at the time // the DocumentEvent is fired SwingUtilities.invokeLater( () -> { try { int endPos = component.getDocument().getLength(); Rectangle rect = component.modelToView(endPos); if (rect != null && rect.y != lastHeight) { setPreferredWidth(); repaint(); lastHeight = rect.y; } } catch (BadLocationException ex) { /* nothing to do */ } }); }