/** * Provides a mapping from the document model coordinate space to the coordinate space of the view * mapped to it. * * @param pos the position to convert >= 0 * @param a the allocated region to render into * @param b a bias value of either <code>Position.Bias.Forward</code> or <code> * Position.Bias.Backward</code> * @return the bounding box of the given position * @exception BadLocationException if the given position does not represent a valid location in * the associated document * @see View#modelToView */ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { boolean isBackward = (b == Position.Bias.Backward); int testPos = (isBackward) ? Math.max(0, pos - 1) : pos; if (isBackward && testPos < getStartOffset()) { return null; } int vIndex = getViewIndexAtPosition(testPos); if ((vIndex != -1) && (vIndex < getViewCount())) { View v = getView(vIndex); if (v != null && testPos >= v.getStartOffset() && testPos < v.getEndOffset()) { Shape childShape = getChildAllocation(vIndex, a); if (childShape == null) { // We are likely invalid, fail. return null; } Shape retShape = v.modelToView(pos, childShape, b); if (retShape == null && v.getEndOffset() == pos) { if (++vIndex < getViewCount()) { v = getView(vIndex); retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b); } } return retShape; } } throw new BadLocationException("Position not represented by view", pos); }
/** * Fetches the child view index representing the given position in the model. * * @param pos the position >= 0 * @return index of the view representing the given position, or -1 if no view represents that * position */ protected int getViewIndexAtPosition(int pos) { if (pos >= getStartOffset() && (pos < getEndOffset())) { for (int counter = getViewCount() - 1; counter >= 0; counter--) { View v = getView(counter); if (pos >= v.getStartOffset() && pos < v.getEndOffset()) { return counter; } } } return -1; }
/** * Creates a view that can be used to represent the current piece of the flow. This can be * either an entire view from the logical view, or a fragment of the logical view. * * @param fv the view holding the flow * @param startOffset the start location for the view being created * @param spanLeft the about of span left to fill in the row * @param rowIndex the row the view will be placed into */ protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) { // Get the child view that contains the given starting position View lv = getLogicalView(fv); int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward); View v = lv.getView(childIndex); if (startOffset == v.getStartOffset()) { // return the entire view return v; } // return a fragment. v = v.createFragment(startOffset, v.getEndOffset()); return v; }
/** * Adjusts the given row if possible to fit within the layout span. By default this will try to * find the highest break weight possible nearest the end of the row. If a forced break is * encountered, the break will be positioned there. * * @param rowIndex the row to adjust to the current layout span. * @param desiredSpan the current layout span >= 0 * @param x the location r starts at. */ protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) { final int flowAxis = fv.getFlowAxis(); View r = fv.getView(rowIndex); int n = r.getViewCount(); int span = 0; int bestWeight = BadBreakWeight; int bestSpan = 0; int bestIndex = -1; int bestOffset = 0; View v; for (int i = 0; i < n; i++) { v = r.getView(i); int spanLeft = desiredSpan - span; int w = v.getBreakWeight(flowAxis, x + span, spanLeft); if ((w >= bestWeight) && (w > BadBreakWeight)) { bestWeight = w; bestIndex = i; bestSpan = span; if (w >= ForcedBreakWeight) { // it's a forced break, so there is // no point in searching further. break; } } span += v.getPreferredSpan(flowAxis); } if (bestIndex < 0) { // there is nothing that can be broken, leave // it in it's current state. return; } // Break the best candidate view, and patch up the row. int spanLeft = desiredSpan - bestSpan; v = r.getView(bestIndex); v = v.breakView(flowAxis, v.getStartOffset(), x + bestSpan, spanLeft); View[] va = new View[1]; va[0] = v; View lv = getLogicalView(fv); for (int i = bestIndex; i < n; i++) { View tmpView = r.getView(i); if (contains(lv, tmpView)) { tmpView.setParent(lv); } else if (tmpView.getViewCount() > 0) { recursiveReparent(tmpView, lv); } } r.replace(bestIndex, n - bestIndex, va); }
/** * Forwards the given <code>DocumentEvent</code> to the child views that need to be notified of * the change to the model. If there were changes to the element this view is responsible for, * that should be considered when forwarding (i.e. new child views should not get notified). * * @param ec changes to the element this view is responsible for (may be <code>null</code> if * there were no changes). * @param e the change information from the associated document * @param a the current allocation of the view * @param f the factory to use to rebuild if the view has children * @see #insertUpdate * @see #removeUpdate * @see #changedUpdate * @since 1.3 */ protected void forwardUpdate( DocumentEvent.ElementChange ec, DocumentEvent e, Shape a, ViewFactory f) { Element elem = getElement(); int pos = e.getOffset(); int index0 = getViewIndex(pos, Position.Bias.Forward); if (index0 == -1 && e.getType() == DocumentEvent.EventType.REMOVE && pos >= getEndOffset()) { // Event beyond our offsets. We may have represented this, that is // the remove may have removed one of our child Elements that // represented this, so, we should foward to last element. index0 = getViewCount() - 1; } int index1 = index0; View v = (index0 >= 0) ? getView(index0) : null; if (v != null) { if ((v.getStartOffset() == pos) && (pos > 0)) { // If v is at a boundary, forward the event to the previous // view too. index0 = Math.max(index0 - 1, 0); } } if (e.getType() != DocumentEvent.EventType.REMOVE) { index1 = getViewIndex(pos + e.getLength(), Position.Bias.Forward); if (index1 < 0) { index1 = getViewCount() - 1; } } int hole0 = index1 + 1; int hole1 = hole0; Element[] addedElems = (ec != null) ? ec.getChildrenAdded() : null; if ((addedElems != null) && (addedElems.length > 0)) { hole0 = ec.getIndex(); hole1 = hole0 + addedElems.length - 1; } // forward to any view not in the forwarding hole // formed by added elements (i.e. they will be updated // by initialization. index0 = Math.max(index0, 0); for (int i = index0; i <= index1; i++) { if (!((i >= hole0) && (i <= hole1))) { v = getView(i); if (v != null) { Shape childAlloc = getChildAllocation(i, a); forwardUpdateToView(v, e, childAlloc, f); } } } }
/** * Paints a portion of a highlight. * * @param g the graphics context * @param offs0 the starting model offset >= 0 * @param offs1 the ending model offset >= offs1 * @param bounds the bounding box of the view, which is not necessarily the region to paint. * @param c the editor * @param view View painting for * @return region drawing occurred in */ public Shape paintLayer( Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c, View view) { Color color = getColor(); if (color == null) { g.setColor(c.getSelectionColor()); } else { g.setColor(color); } Rectangle r; if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) { // Contained in view, can just use bounds. if (bounds instanceof Rectangle) { r = (Rectangle) bounds; } else { r = bounds.getBounds(); } } else { // Should only render part of View. try { // --- determine locations --- Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1, Position.Bias.Backward, bounds); r = (shape instanceof Rectangle) ? (Rectangle) shape : shape.getBounds(); } catch (BadLocationException e) { // can't render r = null; } } if (r != null) { // If we are asked to highlight, we should draw something even // if the model-to-view projection is of zero width (6340106). r.width = Math.max(r.width, 1); g.fillRect(r.x, r.y, r.width, r.height); } return r; }
/** * Provides a mapping from the document model coordinate space to the coordinate space of the view * mapped to it. * * @param p0 the position to convert >= 0 * @param b0 the bias toward the previous character or the next character represented by p0, in * case the position is a boundary of two views; either <code>Position.Bias.Forward</code> or * <code>Position.Bias.Backward</code> * @param p1 the position to convert >= 0 * @param b1 the bias toward the previous character or the next character represented by p1, in * case the position is a boundary of two views * @param a the allocated region to render into * @return the bounding box of the given position is returned * @exception BadLocationException if the given position does not represent a valid location in * the associated document * @exception IllegalArgumentException for an invalid bias argument * @see View#viewToModel */ public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException { if (p0 == getStartOffset() && p1 == getEndOffset()) { return a; } Rectangle alloc = getInsideAllocation(a); Rectangle r0 = new Rectangle(alloc); View v0 = getViewAtPosition((b0 == Position.Bias.Backward) ? Math.max(0, p0 - 1) : p0, r0); Rectangle r1 = new Rectangle(alloc); View v1 = getViewAtPosition((b1 == Position.Bias.Backward) ? Math.max(0, p1 - 1) : p1, r1); if (v0 == v1) { if (v0 == null) { return a; } // Range contained in one view return v0.modelToView(p0, b0, p1, b1, r0); } // Straddles some views. int viewCount = getViewCount(); int counter = 0; while (counter < viewCount) { View v; // Views may not be in same order as model. // v0 or v1 may be null if there is a gap in the range this // view contains. if ((v = getView(counter)) == v0 || v == v1) { View endView; Rectangle retRect; Rectangle tempRect = new Rectangle(); if (v == v0) { retRect = v0.modelToView(p0, b0, v0.getEndOffset(), Position.Bias.Backward, r0).getBounds(); endView = v1; } else { retRect = v1.modelToView(v1.getStartOffset(), Position.Bias.Forward, p1, b1, r1).getBounds(); endView = v0; } // Views entirely covered by range. while (++counter < viewCount && (v = getView(counter)) != endView) { tempRect.setBounds(alloc); childAllocation(counter, tempRect); retRect.add(tempRect); } // End view. if (endView != null) { Shape endShape; if (endView == v1) { endShape = v1.modelToView(v1.getStartOffset(), Position.Bias.Forward, p1, b1, r1); } else { endShape = v0.modelToView(p0, b0, v0.getEndOffset(), Position.Bias.Backward, r0); } if (endShape instanceof Rectangle) { retRect.add((Rectangle) endShape); } else { retRect.add(endShape.getBounds()); } } return retRect; } counter++; } throw new BadLocationException("Position not represented by view", p0); }
/** * Draws a single view (i.e., a line of text for a wrapped view), wrapping the text onto multiple * lines if necessary. Any selected text is rendered with the editor's "selected text" color. * * @param painter The painter to use to render tokens. * @param g The graphics context in which to paint. * @param r The rectangle in which to paint. * @param view The <code>View</code> to paint. * @param fontHeight The height of the font being used. * @param y The y-coordinate at which to begin painting. * @param selStart The start of the selection. * @param selEnd The end of the selection. */ protected void drawViewWithSelection( TokenPainter painter, Graphics2D g, Rectangle r, View view, int fontHeight, int y, int selStart, int selEnd) { float x = r.x; LayeredHighlighter h = (LayeredHighlighter) host.getHighlighter(); RSyntaxDocument document = (RSyntaxDocument) getDocument(); Element map = getElement(); int p0 = view.getStartOffset(); int lineNumber = map.getElementIndex(p0); int p1 = view.getEndOffset(); // - 1; setSegment(p0, p1 - 1, document, drawSeg); // System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")"); int start = p0 - drawSeg.offset; Token token = document.getTokenListForLine(lineNumber); // If this line is an empty line, then the token list is simply a // null token. In this case, the line highlight will be skipped in // the loop below, so unfortunately we must manually do it here. if (token != null && token.getType() == Token.NULL) { h.paintLayeredHighlights(g, p0, p1, r, host, this); return; } // Loop through all tokens in this view and paint them! while (token != null && token.isPaintable()) { int p = calculateBreakPosition(p0, token, x); x = r.x; h.paintLayeredHighlights(g, p0, p, r, host, this); while (token != null && token.isPaintable() && token.getEndOffset() - 1 < p) { // <=p) { // Selection starts in this token if (token.containsPosition(selStart)) { if (selStart > token.getOffset()) { tempToken.copyFrom(token); tempToken.textCount = selStart - tempToken.getOffset(); x = painter.paint(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(selStart); // Clone required since token and tempToken must be // different tokens for else statement below token = new TokenImpl(tempToken); } int selCount = Math.min(token.length(), selEnd - token.getOffset()); if (selCount == token.length()) { x = painter.paintSelected(token, g, x, y, host, this); } else { tempToken.copyFrom(token); tempToken.textCount = selCount; x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(token.getOffset() + selCount); token = tempToken; x = painter.paint(token, g, x, y, host, this); } } // Selection ends in this token else if (token.containsPosition(selEnd)) { tempToken.copyFrom(token); tempToken.textCount = selEnd - tempToken.getOffset(); x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(selEnd); token = tempToken; x = painter.paint(token, g, x, y, host, this); } // This token is entirely selected else if (token.getOffset() >= selStart && token.getEndOffset() <= selEnd) { x = painter.paintSelected(token, g, x, y, host, this); } // This token is entirely unselected else { x = painter.paint(token, g, x, y, host, this); } token = token.getNextToken(); } // If there's a token that's going to be split onto the next line if (token != null && token.isPaintable() && token.getOffset() < p) { int tokenOffset = token.getOffset(); Token orig = token; token = new TokenImpl( drawSeg, tokenOffset - start, p - 1 - start, tokenOffset, token.getType()); // Selection starts in this token if (token.containsPosition(selStart)) { if (selStart > token.getOffset()) { tempToken.copyFrom(token); tempToken.textCount = selStart - tempToken.getOffset(); x = painter.paint(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(selStart); // Clone required since token and tempToken must be // different tokens for else statement below token = new TokenImpl(tempToken); } int selCount = Math.min(token.length(), selEnd - token.getOffset()); if (selCount == token.length()) { x = painter.paintSelected(token, g, x, y, host, this); } else { tempToken.copyFrom(token); tempToken.textCount = selCount; x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(token.getOffset() + selCount); token = tempToken; x = painter.paint(token, g, x, y, host, this); } } // Selection ends in this token else if (token.containsPosition(selEnd)) { tempToken.copyFrom(token); tempToken.textCount = selEnd - tempToken.getOffset(); x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(selEnd); token = tempToken; x = painter.paint(token, g, x, y, host, this); } // This token is entirely selected else if (token.getOffset() >= selStart && token.getEndOffset() <= selEnd) { x = painter.paintSelected(token, g, x, y, host, this); } // This token is entirely unselected else { x = painter.paint(token, g, x, y, host, this); } token = new TokenImpl(orig); ((TokenImpl) token).makeStartAt(p); } p0 = (p == p0) ? p1 : p; y += fontHeight; } // End of while (token!=null && token.isPaintable()). // NOTE: We should re-use code from Token (paintBackground()) here, // but don't because I'm just too lazy. if (host.getEOLMarkersVisible()) { g.setColor(host.getForegroundForTokenType(Token.WHITESPACE)); g.setFont(host.getFontForTokenType(Token.WHITESPACE)); g.drawString("\u00B6", x, y - fontHeight); } }
/** * Draws a single view (i.e., a line of text for a wrapped view), wrapping the text onto multiple * lines if necessary. * * @param painter The painter to use to render tokens. * @param g The graphics context in which to paint. * @param r The rectangle in which to paint. * @param view The <code>View</code> to paint. * @param fontHeight The height of the font being used. * @param y The y-coordinate at which to begin painting. */ protected void drawView( TokenPainter painter, Graphics2D g, Rectangle r, View view, int fontHeight, int y) { float x = r.x; LayeredHighlighter h = (LayeredHighlighter) host.getHighlighter(); RSyntaxDocument document = (RSyntaxDocument) getDocument(); Element map = getElement(); int p0 = view.getStartOffset(); int lineNumber = map.getElementIndex(p0); int p1 = view.getEndOffset(); // - 1; setSegment(p0, p1 - 1, document, drawSeg); // System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")"); int start = p0 - drawSeg.offset; Token token = document.getTokenListForLine(lineNumber); // If this line is an empty line, then the token list is simply a // null token. In this case, the line highlight will be skipped in // the loop below, so unfortunately we must manually do it here. if (token != null && token.getType() == Token.NULL) { h.paintLayeredHighlights(g, p0, p1, r, host, this); return; } // Loop through all tokens in this view and paint them! while (token != null && token.isPaintable()) { int p = calculateBreakPosition(p0, token, x); x = r.x; h.paintLayeredHighlights(g, p0, p, r, host, this); while (token != null && token.isPaintable() && token.getEndOffset() - 1 < p) { // <=p) { x = painter.paint(token, g, x, y, host, this); token = token.getNextToken(); } if (token != null && token.isPaintable() && token.getOffset() < p) { int tokenOffset = token.getOffset(); tempToken.set( drawSeg.array, tokenOffset - start, p - 1 - start, tokenOffset, token.getType()); painter.paint(tempToken, g, x, y, host, this); tempToken.copyFrom(token); tempToken.makeStartAt(p); token = new TokenImpl(tempToken); } p0 = (p == p0) ? p1 : p; y += fontHeight; } // End of while (token!=null && token.isPaintable()). // NOTE: We should re-use code from Token (paintBackground()) here, // but don't because I'm just too lazy. if (host.getEOLMarkersVisible()) { g.setColor(host.getForegroundForTokenType(Token.WHITESPACE)); g.setFont(host.getFontForTokenType(Token.WHITESPACE)); g.drawString("\u00B6", x, y - fontHeight); } }