public Shape modelToView(GlyphView v, int pos, Position.Bias bias, Shape a) throws BadLocationException { sync(v); Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds(); int p0 = v.getStartOffset(); int p1 = v.getEndOffset(); TabExpander expander = v.getTabExpander(); Segment text; if (pos == p1) { // The caller of this is left to right and borders a right to // left view, return our end location. return new Rectangle(alloc.x + alloc.width, alloc.y, 0, metrics.getHeight()); } if ((pos >= p0) && (pos <= p1)) { // determine range to the left of the position text = v.getText(p0, pos); int[] justificationData = getJustificationData(v); int width = Utilities.getTabbedTextWidth(v, text, metrics, alloc.x, expander, p0, justificationData); SegmentCache.releaseSharedSegment(text); return new Rectangle(alloc.x + width, alloc.y, 0, metrics.getHeight()); } throw new BadLocationException("modelToView - can't convert", p1); }
/** Paints the glyphs representing the given range. */ public void paint(GlyphView v, Graphics g, Shape a, int p0, int p1) { sync(v); Segment text; TabExpander expander = v.getTabExpander(); Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds(); // determine the x coordinate to render the glyphs int x = alloc.x; int p = v.getStartOffset(); int[] justificationData = getJustificationData(v); if (p != p0) { text = v.getText(p, p0); int width = Utilities.getTabbedTextWidth(v, text, metrics, x, expander, p, justificationData); x += width; SegmentCache.releaseSharedSegment(text); } // determine the y coordinate to render the glyphs int y = alloc.y + metrics.getHeight() - metrics.getDescent(); // render the glyphs text = v.getText(p0, p1); g.setFont(metrics.getFont()); Utilities.drawTabbedText(v, text, x, y, g, expander, p0, justificationData); SegmentCache.releaseSharedSegment(text); }
@SuppressWarnings("deprecation") void sync(GlyphView v) { Font f = v.getFont(); if ((metrics == null) || (!f.equals(metrics.getFont()))) { // fetch a new FontMetrics Container c = v.getContainer(); metrics = (c != null) ? c.getFontMetrics(f) : Toolkit.getDefaultToolkit().getFontMetrics(f); } }
/** * Determines the best location (in the model) to break the given view. This method attempts to * break on a whitespace location. If a whitespace location can't be found, the nearest character * location is returned. * * @param v the view * @param p0 the location in the model where the fragment should start its representation >= 0 * @param pos the graphic location along the axis that the broken view would occupy >= 0; this may * be useful for things like tab calculations * @param len specifies the distance into the view where a potential break is desired >= 0 * @return the model location desired for a break * @see View#breakView */ public int getBoundedPosition(GlyphView v, int p0, float x, float len) { sync(v); TabExpander expander = v.getTabExpander(); Segment s = v.getText(p0, v.getEndOffset()); int[] justificationData = getJustificationData(v); int index = Utilities.getTabbedTextOffset( v, s, metrics, (int) x, (int) (x + len), expander, p0, false, justificationData); SegmentCache.releaseSharedSegment(s); int p1 = p0 + index; return p1; }
/** * Provides a mapping from the view coordinate space to the logical coordinate space of the model. * * @param v the view containing the view coordinates * @param x the X coordinate * @param y the Y coordinate * @param a the allocated region to render into * @param biasReturn either <code>Position.Bias.Forward</code> or <code>Position.Bias.Backward * </code> is returned as the zero-th element of this array * @return the location within the model that best represents the given point of view * @see View#viewToModel */ public int viewToModel(GlyphView v, float x, float y, Shape a, Position.Bias[] biasReturn) { Rectangle2D alloc = (a instanceof Rectangle2D) ? (Rectangle2D) a : a.getBounds2D(); // Move the y co-ord of the hit onto the baseline. This is because TextLayout supports // italic carets and we do not. TextHitInfo hit = layout.hitTestChar(x - (float) alloc.getX(), 0); int pos = hit.getInsertionIndex(); if (pos == v.getEndOffset()) { pos--; } biasReturn[0] = hit.isLeadingEdge() ? Position.Bias.Forward : Position.Bias.Backward; return pos + v.getStartOffset(); }
/** Determine the span the glyphs given a start location (for tab expansion). */ public float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x) { sync(v); Segment text = v.getText(p0, p1); int[] justificationData = getJustificationData(v); int width = Utilities.getTabbedTextWidth(v, text, metrics, (int) x, e, p0, justificationData); SegmentCache.releaseSharedSegment(text); return width; }
/** * Determine the span the glyphs given a start location (for tab expansion). This implementation * assumes it has no tabs (i.e. TextLayout doesn't deal with tab expansion). */ public float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x) { if ((p0 == v.getStartOffset()) && (p1 == v.getEndOffset())) { return layout.getAdvance(); } int p = v.getStartOffset(); int index0 = p0 - p; int index1 = p1 - p; TextHitInfo hit0 = TextHitInfo.afterOffset(index0); TextHitInfo hit1 = TextHitInfo.beforeOffset(index1); float[] locs = layout.getCaretInfo(hit0); float x0 = locs[0]; locs = layout.getCaretInfo(hit1); float x1 = locs[0]; return (x1 > x0) ? x1 - x0 : x0 - x1; }
/** * @return justificationData from the ParagraphRow this GlyphView is in or {@code null} if no * justification is needed */ private int[] getJustificationData(GlyphView v) { View parent = v.getParent(); int[] ret = null; if (parent instanceof ParagraphView.Row) { ParagraphView.Row row = ((ParagraphView.Row) parent); ret = row.justificationData; } return ret; }
/** * Determines the model location that represents the maximum advance that fits within the given * span. This could be used to break the given view. The result should be a location just shy of * the given advance. This differs from viewToModel which returns the closest position which might * be proud of the maximum advance. * * @param v the view to find the model location to break at. * @param p0 the location in the model where the fragment should start it's representation >= 0. * @param pos the graphic location along the axis that the broken view would occupy >= 0. This may * be useful for things like tab calculations. * @param len specifies the distance into the view where a potential break is desired >= 0. * @return the maximum model location possible for a break. * @see View#breakView */ public int getBoundedPosition(GlyphView v, int p0, float x, float len) { if (len < 0) throw new IllegalArgumentException("Length must be >= 0."); // note: this only works because swing uses TextLayouts that are // only pure rtl or pure ltr TextHitInfo hit; if (layout.isLeftToRight()) { hit = layout.hitTestChar(len, 0); } else { hit = layout.hitTestChar(layout.getAdvance() - len, 0); } return v.getStartOffset() + hit.getCharIndex(); }
/** * Provides a mapping from the view coordinate space to the logical coordinate space of the model. * * @param v the view containing the view coordinates * @param x the X coordinate * @param y the Y coordinate * @param a the allocated region to render into * @param biasReturn always returns <code>Position.Bias.Forward</code> as the zero-th element of * this array * @return the location within the model that best represents the given point in the view * @see View#viewToModel */ public int viewToModel(GlyphView v, float x, float y, Shape a, Position.Bias[] biasReturn) { sync(v); Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds(); int p0 = v.getStartOffset(); int p1 = v.getEndOffset(); TabExpander expander = v.getTabExpander(); Segment text = v.getText(p0, p1); int[] justificationData = getJustificationData(v); int offs = Utilities.getTabbedTextOffset( v, text, metrics, alloc.x, (int) x, expander, p0, justificationData); SegmentCache.releaseSharedSegment(text); int retValue = p0 + offs; if (retValue == p1) { // No need to return backward bias as GlyphPainter1 is used for // ltr text only. retValue--; } biasReturn[0] = Position.Bias.Forward; return retValue; }
/** * Paint the glyphs for the given view. This is implemented to only render if the Graphics is of * type Graphics2D which is required by TextLayout (and this should be the case if running on the * JDK). */ public void paint(GlyphView v, Graphics g, Shape a, int p0, int p1) { if (g instanceof Graphics2D) { Rectangle2D alloc = a.getBounds2D(); Graphics2D g2d = (Graphics2D) g; float y = (float) alloc.getY() + layout.getAscent() + layout.getLeading(); float x = (float) alloc.getX(); if (p0 > v.getStartOffset() || p1 < v.getEndOffset()) { try { // TextLayout can't render only part of it's range, so if a // partial range is required, add a clip region. Shape s = v.modelToView(p0, Position.Bias.Forward, p1, Position.Bias.Backward, a); Shape savedClip = g.getClip(); g2d.clip(s); layout.draw(g2d, x, y); g.setClip(savedClip); } catch (BadLocationException e) { } } else { layout.draw(g2d, x, y); } } }
public Shape modelToView(GlyphView v, int pos, Position.Bias bias, Shape a) throws BadLocationException { int offs = pos - v.getStartOffset(); Rectangle2D alloc = a.getBounds2D(); TextHitInfo hit = (bias == Position.Bias.Forward) ? TextHitInfo.afterOffset(offs) : TextHitInfo.beforeOffset(offs); float[] locs = layout.getCaretInfo(hit); // vertical at the baseline, should use slope and check if glyphs // are being rendered vertically. alloc.setRect(alloc.getX() + locs[0], alloc.getY(), 1, alloc.getHeight()); return alloc; }
/** * Provides a way to determine the next visually represented model location that 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 v the view to use * @param pos the position to convert >= 0 * @param a the allocated region to render into * @param direction the direction from the current position that can be thought of as the arrow * keys typically found on a keyboard. This may be SwingConstants.WEST, SwingConstants.EAST, * SwingConstants.NORTH, or SwingConstants.SOUTH. * @return the location within the model that best represents the next location visual position. * @exception BadLocationException * @exception IllegalArgumentException for an invalid direction */ public int getNextVisualPositionFrom( GlyphView v, int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) throws BadLocationException { int startOffset = v.getStartOffset(); int endOffset = v.getEndOffset(); Segment text; AbstractDocument doc; boolean viewIsLeftToRight; TextHitInfo currentHit, nextHit; switch (direction) { case View.NORTH: break; case View.SOUTH: break; case View.EAST: doc = (AbstractDocument) v.getDocument(); viewIsLeftToRight = doc.isLeftToRight(startOffset, endOffset); if (startOffset == doc.getLength()) { if (pos == -1) { biasRet[0] = Position.Bias.Forward; return startOffset; } // End case for bidi text where newline is at beginning // of line. return -1; } if (pos == -1) { // Entering view from the left. if (viewIsLeftToRight) { biasRet[0] = Position.Bias.Forward; return startOffset; } else { text = v.getText(endOffset - 1, endOffset); char c = text.array[text.offset]; SegmentCache.releaseSharedSegment(text); if (c == '\n') { biasRet[0] = Position.Bias.Forward; return endOffset - 1; } biasRet[0] = Position.Bias.Backward; return endOffset; } } if (b == Position.Bias.Forward) currentHit = TextHitInfo.afterOffset(pos - startOffset); else currentHit = TextHitInfo.beforeOffset(pos - startOffset); nextHit = layout.getNextRightHit(currentHit); if (nextHit == null) { return -1; } if (viewIsLeftToRight != layout.isLeftToRight()) { // If the layout's base direction is different from // this view's run direction, we need to use the weak // carrat. nextHit = layout.getVisualOtherHit(nextHit); } pos = nextHit.getInsertionIndex() + startOffset; if (pos == endOffset) { // A move to the right from an internal position will // only take us to the endOffset in a left to right run. text = v.getText(endOffset - 1, endOffset); char c = text.array[text.offset]; SegmentCache.releaseSharedSegment(text); if (c == '\n') { return -1; } biasRet[0] = Position.Bias.Backward; } else { biasRet[0] = Position.Bias.Forward; } return pos; case View.WEST: doc = (AbstractDocument) v.getDocument(); viewIsLeftToRight = doc.isLeftToRight(startOffset, endOffset); if (startOffset == doc.getLength()) { if (pos == -1) { biasRet[0] = Position.Bias.Forward; return startOffset; } // End case for bidi text where newline is at beginning // of line. return -1; } if (pos == -1) { // Entering view from the right if (viewIsLeftToRight) { text = v.getText(endOffset - 1, endOffset); char c = text.array[text.offset]; SegmentCache.releaseSharedSegment(text); if ((c == '\n') || Character.isSpaceChar(c)) { biasRet[0] = Position.Bias.Forward; return endOffset - 1; } biasRet[0] = Position.Bias.Backward; return endOffset; } else { biasRet[0] = Position.Bias.Forward; return startOffset; } } if (b == Position.Bias.Forward) currentHit = TextHitInfo.afterOffset(pos - startOffset); else currentHit = TextHitInfo.beforeOffset(pos - startOffset); nextHit = layout.getNextLeftHit(currentHit); if (nextHit == null) { return -1; } if (viewIsLeftToRight != layout.isLeftToRight()) { // If the layout's base direction is different from // this view's run direction, we need to use the weak // carrat. nextHit = layout.getVisualOtherHit(nextHit); } pos = nextHit.getInsertionIndex() + startOffset; if (pos == endOffset) { // A move to the left from an internal position will // only take us to the endOffset in a right to left run. text = v.getText(endOffset - 1, endOffset); char c = text.array[text.offset]; SegmentCache.releaseSharedSegment(text); if (c == '\n') { return -1; } biasRet[0] = Position.Bias.Backward; } else { biasRet[0] = Position.Bias.Forward; } return pos; default: throw new IllegalArgumentException("Bad direction: " + direction); } return pos; }