/** * Update the flow on the given FlowView. By default, this causes all of the rows (child views) * to be rebuilt to match the given constraints for each row. This is called by a * FlowView.layout to update the child views in the flow. * * @param fv the view to reflow */ public void layout(FlowView fv) { int p0 = fv.getStartOffset(); int p1 = fv.getEndOffset(); // we want to preserve all views from the logicalView from being // removed View lv = getLogicalView(fv); int n = lv.getViewCount(); for (int i = 0; i < n; i++) { View v = lv.getView(i); v.setParent(lv); } fv.removeAll(); for (int rowIndex = 0; p0 < p1; rowIndex++) { View row = fv.createRow(); fv.append(row); // layout the row to the current span. If nothing fits, // force something. int next = layoutRow(fv, rowIndex, p0); if (row.getViewCount() == 0) { row.append(createView(fv, p0, Integer.MAX_VALUE, rowIndex)); next = row.getEndOffset(); } if (next <= p0) { throw new StateInvariantError("infinite loop in formatting"); } else { p0 = next; } } }
/** * 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); }
private boolean contains(View logicalView, View v) { int n = logicalView.getViewCount(); for (int i = 0; i < n; i++) { if (logicalView.getView(i) == v) { return true; } } return false; }
private void recursiveReparent(View v, View logicalView) { int n = v.getViewCount(); for (int i = 0; i < n; i++) { View tmpView = v.getView(i); if (contains(logicalView, tmpView)) { tmpView.setParent(logicalView); } else { recursiveReparent(tmpView, logicalView); } } }
/** * GlyphGutter copy pasted bolerplate method. It invokes {@link #paintView} that contains actual * business logic. */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); Rectangle clip = g.getClipBounds(); JTextComponent component = editorUI.getComponent(); if (component == null) return; BaseTextUI textUI = (BaseTextUI) component.getUI(); View rootView = Utilities.getDocumentView(component); if (rootView == null) return; g.setColor(backgroundColor()); g.fillRect(clip.x, clip.y, clip.width, clip.height); AbstractDocument doc = (AbstractDocument) component.getDocument(); doc.readLock(); try { foldHierarchy.lock(); try { int startPos = textUI.getPosFromY(clip.y); int startViewIndex = rootView.getViewIndex(startPos, Position.Bias.Forward); int rootViewCount = rootView.getViewCount(); if (startViewIndex >= 0 && startViewIndex < rootViewCount) { int clipEndY = clip.y + clip.height; for (int i = startViewIndex; i < rootViewCount; i++) { View view = rootView.getView(i); Rectangle rec = component.modelToView(view.getStartOffset()); if (rec == null) { break; } int y = rec.y; paintView(view, g, y); if (y >= clipEndY) { break; } } } } finally { foldHierarchy.unlock(); } } catch (BadLocationException ble) { Mercurial.LOG.log(Level.WARNING, null, ble); } finally { doc.readUnlock(); } }
/** Returns the baseline for single line text components, like <code>JTextField</code>. */ private static int getSingleLineTextBaseline(JTextComponent textComponent, int h) { View rootView = textComponent.getUI().getRootView(textComponent); if (rootView.getViewCount() > 0) { Insets insets = textComponent.getInsets(); int height = h - insets.top - insets.bottom; int y = insets.top; View fieldView = rootView.getView(0); int vspan = (int) fieldView.getPreferredSpan(View.Y_AXIS); if (height != vspan) { int slop = height - vspan; y += slop / 2; } FontMetrics fm = textComponent.getFontMetrics(textComponent.getFont()); y += fm.getAscent(); return y; } return -1; }
/** * Creates a row of views that will fit within the layout span of the row. This is called by the * layout method. This is implemented to fill the row by repeatedly calling the createView * method until the available span has been exhausted, a forced break was encountered, or the * createView method returned null. If the remaining span was exhaused, the adjustRow method * will be called to perform adjustments to the row to try and make it fit into the given span. * * @param rowIndex the index of the row to fill in with views. The row is assumed to be empty on * entry. * @param pos The current position in the children of this views element from which to start. * @return the position to start the next row */ protected int layoutRow(FlowView fv, int rowIndex, int pos) { View row = fv.getView(rowIndex); int x = fv.getFlowStart(rowIndex); int spanLeft = fv.getFlowSpan(rowIndex); int end = fv.getEndOffset(); TabExpander te = (fv instanceof TabExpander) ? (TabExpander) fv : null; // Indentation. int preX = x; int availableSpan = spanLeft; preX = x; final int flowAxis = fv.getFlowAxis(); boolean forcedBreak = false; while (pos < end && spanLeft > 0) { View v = createView(fv, pos, spanLeft, rowIndex); if (v == null) { break; } int chunkSpan; if ((flowAxis == X_AXIS) && (v instanceof TabableView)) { chunkSpan = (int) ((TabableView) v).getTabbedSpan(x, te); } else { chunkSpan = (int) v.getPreferredSpan(flowAxis); } // If a forced break is necessary, break if (v.getBreakWeight(flowAxis, pos, spanLeft) >= ForcedBreakWeight) { int n = row.getViewCount(); if (n > 0) { /* If this is a forced break and it's not the only view * the view should be replaced with a call to breakView. * If it's it only view, it should be used directly. In * either case no more children should be added beyond this * view. */ v = v.breakView(flowAxis, pos, x, spanLeft); if (v != null) { if ((flowAxis == X_AXIS) && (v instanceof TabableView)) { chunkSpan = (int) ((TabableView) v).getTabbedSpan(x, te); } else { chunkSpan = (int) v.getPreferredSpan(flowAxis); } } else { chunkSpan = 0; } } forcedBreak = true; } spanLeft -= chunkSpan; x += chunkSpan; if (v != null) { row.append(v); pos = v.getEndOffset(); } if (forcedBreak) { break; } } if (spanLeft < 0) { // This row is too long and needs to be adjusted. adjustRow(fv, rowIndex, availableSpan, preX); } else if (row.getViewCount() == 0) { // Impossible spec... put in whatever is left. View v = createView(fv, pos, Integer.MAX_VALUE, rowIndex); row.append(v); } return row.getEndOffset(); }