/** * Repaints area of the control corresponding to a range of offsets in the document. * * @param startOffset Starting offset of the range. * @param endOffset Ending offset of the range. */ private void repaintRange(int startOffset, int endOffset) { Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); Rectangle startBounds = this.rootBox.getCaret(context, startOffset).getBounds(); int top1 = startBounds.getY(); int bottom1 = top1 + startBounds.getHeight(); Rectangle endBounds = this.rootBox.getCaret(context, endOffset).getBounds(); int top2 = endBounds.getY(); int bottom2 = top2 + endBounds.getHeight(); int top = Math.min(top1, top2); int bottom = Math.max(bottom1, bottom2); if (top == bottom) { // Account for zero-height horizontal carets this.hostComponent.repaint(0, top - 1, this.getLayoutWidth(), bottom - top + 1); } else { this.hostComponent.repaint(0, top, this.getLayoutWidth(), bottom - top); } g.dispose(); }
/** * Calls layout() on the rootBox until the y-coordinate of a caret at the given offset converges, * i.e. is less than LAYOUT_TOLERANCE pixels from the last call. * * @param offset Offset around which we should lay out boxes. */ private void iterateLayout(int offset) { int repaintStart = Integer.MAX_VALUE; int repaintEnd = 0; Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); int layoutY = this.rootBox.getCaret(context, offset).getY(); while (true) { int oldLayoutY = layoutY; IntRange repaintRange = this.rootBox.layout(context, layoutY - LAYOUT_WINDOW / 2, layoutY + LAYOUT_WINDOW / 2); if (repaintRange != null) { repaintStart = Math.min(repaintStart, repaintRange.getStart()); repaintEnd = Math.max(repaintEnd, repaintRange.getEnd()); } layoutY = this.rootBox.getCaret(context, offset).getY(); if (Math.abs(layoutY - oldLayoutY) < LAYOUT_TOLERANCE) { break; } } g.dispose(); if (repaintStart < repaintEnd) { Rectangle viewport = this.hostComponent.getViewport(); if (repaintStart < viewport.getY() + viewport.getHeight() && repaintEnd > viewport.getY()) { int start = Math.max(repaintStart, viewport.getY()); int end = Math.min(repaintEnd, viewport.getY() + viewport.getHeight()); this.hostComponent.repaint(viewport.getX(), start, viewport.getWidth(), end - start); } } }
public int viewToModel(int x, int y) { Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); int offset = this.rootBox.viewToModel(context, x, y); g.dispose(); return offset; }
public void moveToPreviousLine(boolean select) { int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX; Graphics g = this.hostComponent.createDefaultGraphics(); int offset = this.rootBox.getPreviousLineOffset(this.createLayoutContext(g), this.getCaretOffset(), x); g.dispose(); this.moveTo(offset, select); this.magicX = x; }
/** Lay out the area around the caret. */ private void relayout() { long start = System.currentTimeMillis(); int oldHeight = this.rootBox.getHeight(); this.iterateLayout(this.getCaretOffset()); if (this.rootBox.getHeight() != oldHeight) { this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight()); } Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); this.caret = this.rootBox.getCaret(context, this.getCaretOffset()); g.dispose(); if (this.isDebugging()) { long end = System.currentTimeMillis(); System.out.println("VexWidget layout took " + (end - start) + "ms"); } }
public void moveTo(int offset, boolean select) { if (offset >= 1 && offset <= this.document.getLength() - 1) { // repaint the selection area, if any this.repaintCaret(); this.repaintRange(this.getSelectionStart(), this.getSelectionEnd()); VEXElement oldElement = this.currentElement; this.caretOffset = offset; this.currentElement = this.document.getElementAt(offset); if (select) { this.selectionStart = Math.min(this.mark, this.caretOffset); this.selectionEnd = Math.max(this.mark, this.caretOffset); // move selectionStart and selectionEnd to make sure we don't // select a partial element VEXElement commonElement = this.document.findCommonElement(this.selectionStart, this.selectionEnd); VEXElement element = this.document.getElementAt(this.selectionStart); while (element != commonElement) { this.selectionStart = element.getStartOffset(); element = this.document.getElementAt(this.selectionStart); } element = this.document.getElementAt(this.selectionEnd); while (element != commonElement) { this.selectionEnd = element.getEndOffset() + 1; element = this.document.getElementAt(this.selectionEnd); } } else { this.mark = offset; this.selectionStart = offset; this.selectionEnd = offset; } if (this.beginWorkCount == 0) { this.relayout(); } Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); this.caret = this.rootBox.getCaret(context, offset); VEXElement element = this.getCurrentElement(); if (element != oldElement) { this.caretColor = Color.BLACK; while (element != null) { Color bgColor = this.styleSheet.getStyles(element).getBackgroundColor(); if (bgColor != null) { int red = ~bgColor.getRed() & 0xff; int green = ~bgColor.getGreen() & 0xff; int blue = ~bgColor.getBlue() & 0xff; this.caretColor = new Color(red, green, blue); break; } element = element.getParent(); } } g.dispose(); this.magicX = -1; this.scrollCaretVisible(); this.hostComponent.fireSelectionChanged(); this.caretVisible = true; this.repaintRange(this.getSelectionStart(), this.getSelectionEnd()); } }
/** * Re-layout the entire widget, due to either a layout width change or a stylesheet range. This * method does the actual setting of the width and stylesheet, since it needs to know where the * caret is <i>before</i> the change, so that it can do a reasonable job of restoring the position * of the viewport after the change. * * @param newWidth New width for the widget. * @param newStyleSheet New stylesheet for the widget. */ private void relayoutAll(int newWidth, StyleSheet newStyleSheet) { Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); Rectangle viewport = this.hostComponent.getViewport(); // true if the caret is within the viewport // // TODO: incorrect if caret near the bottom and the viewport is // shrinking // To fix, we probably need to save the viewport height, just like // we now store viewport width (as layout width). boolean caretVisible = viewport.intersects(this.caret.getBounds()); // distance from the top of the viewport to the top of the caret // use this if the caret is visible in the viewport int relCaretY = 0; // offset around which we are laying out // this is also where we put the top of the viewport if the caret // isn't visible int offset; if (caretVisible) { relCaretY = this.caret.getY() - viewport.getY(); offset = this.getCaretOffset(); } else { offset = this.rootBox.viewToModel(context, 0, viewport.getY()); } this.layoutWidth = newWidth; this.styleSheet = newStyleSheet; // Re-create the context, since it holds the old stylesheet context = this.createLayoutContext(g); this.createRootBox(); this.iterateLayout(offset); this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight()); this.caret = this.rootBox.getCaret(context, this.getCaretOffset()); if (caretVisible) { int viewportY = this.caret.getY() - Math.min(relCaretY, viewport.getHeight()); viewportY = Math.min(this.rootBox.getHeight() - viewport.getHeight(), viewportY); viewportY = Math.max(0, viewportY); // this must appear after the // above line, since // that line might set viewportY negative this.hostComponent.scrollTo(viewport.getX(), viewportY); this.scrollCaretVisible(); } else { int viewportY = this.rootBox.getCaret(context, offset).getY(); this.hostComponent.scrollTo(viewport.getX(), viewportY); } this.hostComponent.repaint(); g.dispose(); }
private void createRootBox() { Graphics g = this.hostComponent.createDefaultGraphics(); LayoutContext context = this.createLayoutContext(g); this.rootBox = new RootBox(context, this.document.getRootElement(), this.getLayoutWidth()); g.dispose(); }