/** * 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); } } }
/** Repaints the area of the caret. */ private void repaintCaret() { if (this.caret != null) { // caret may be null when document is first set Rectangle bounds = this.caret.getBounds(); this.hostComponent.repaint( bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); } }
private void scrollCaretVisible() { Rectangle caretBounds = this.caret.getBounds(); Rectangle viewport = this.hostComponent.getViewport(); int x = viewport.getX(); int y = 0; int offset = getCaretOffset(); if (offset == 1) { y = 0; } else if (offset == getDocument().getLength() - 1) { if (this.rootBox.getHeight() < viewport.getHeight()) { y = 0; } else { y = this.rootBox.getHeight() - viewport.getHeight(); } } else if (caretBounds.getY() < viewport.getY()) { y = caretBounds.getY(); } else if (caretBounds.getY() + caretBounds.getHeight() > viewport.getY() + viewport.getHeight()) { y = caretBounds.getY() + caretBounds.getHeight() - viewport.getHeight(); } else { // no scrolling required return; } this.hostComponent.scrollTo(x, y); }
/** * Paints the contents of the widget in the given Graphics at the given point. * * @param g Graphics in which to draw the widget contents * @param x x-coordinate at which to draw the widget * @param y y-coordinate at which to draw the widget */ public void paint(Graphics g, int x, int y) { if (this.rootBox == null) { return; } LayoutContext context = this.createLayoutContext(g); // Since we may be scrolling to sections of the document that have // yet to be layed out, lay out any exposed area. // // TODO: this will probably be inaccurate, since we should really // iterate the layout, but we don't have an offset around which // to iterate...what to do, what to do.... Rectangle rect = g.getClipBounds(); int oldHeight = this.rootBox.getHeight(); this.rootBox.layout(context, rect.getY(), rect.getY() + rect.getHeight()); if (this.rootBox.getHeight() != oldHeight) { this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight()); } this.rootBox.paint(context, 0, 0); if (this.caretVisible) { this.caret.draw(g, this.caretColor); } // Debug hash marks /* * ColorResource grey = g.createColor(new Color(160, 160, 160)); * ColorResource oldColor = g.setColor(grey); for (int y2 = rect.getY() * - rect.getY() % 50; y2 < rect.getY() + rect.getHeight(); y2 += 50) { * g.drawLine(x, y + y2, x+10, y + y2); * g.drawString(Integer.toString(y2), x + 15, y + y2 - 10); } * g.setColor(oldColor); grey.dispose(); */ }
/** * 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(); }