- /*
 - * @(#)ParagraphView.java 1.76 00/02/02
 - *
 - * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
 - *
 - * This software is the proprietary information of Sun Microsystems, Inc.
 - * Use is subject to license terms.
 - *
 - */
 - package javax.swing.text;
 - import java.awt.*;
 - import javax.swing.event.*;
 - import javax.swing.SizeRequirements;
 - /**
 - * View of a simple line-wrapping paragraph that supports
 - * multiple fonts, colors, components, icons, etc. It is
 - * basically a vertical box with a margin around it. The
 - * contents of the box are a bunch of rows which are special
 - * horizontal boxes. This view creates a collection of
 - * views that represent the child elements of the paragraph
 - * element. Each of these views are placed into a row
 - * directly if they will fit, otherwise the <code>breakView</code>
 - * method is called to try and carve the view into pieces
 - * that fit.
 - *
 - * @author Timothy Prinzing
 - * @author Scott Violet
 - * @version 1.76 02/02/00
 - * @see View
 - */
 - public class ParagraphView extends FlowView implements TabExpander {
 - /**
 - * Constructs a ParagraphView for the given element.
 - *
 - * @param elem the element that this view is responsible for
 - */
 - public ParagraphView(Element elem) {
 - super(elem, View.Y_AXIS);
 - setPropertiesFromAttributes();
 - Document doc = elem.getDocument();
 - Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
 - if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
 - try {
 - if (i18nStrategy == null) {
 - // the classname should probably come from a property file.
 - String classname = "javax.swing.text.TextLayoutStrategy";
 - ClassLoader loader = getClass().getClassLoader();
 - if (loader != null) {
 - i18nStrategy = loader.loadClass(classname);
 - } else {
 - i18nStrategy = Class.forName(classname);
 - }
 - }
 - Object o = i18nStrategy.newInstance();
 - if (o instanceof FlowStrategy) {
 - strategy = (FlowStrategy) o;
 - }
 - } catch (Throwable e) {
 - throw new StateInvariantError("ParagraphView: Can't create i18n strategy: "
 - + e.getMessage());
 - }
 - }
 - }
 - /**
 - * Set the type of justification.
 - */
 - protected void setJustification(int j) {
 - justification = j;
 - }
 - /**
 - * Set the line spacing.
 - *
 - * @param ls the value in points
 - */
 - protected void setLineSpacing(float ls) {
 - lineSpacing = ls;
 - }
 - /**
 - * Set the indent on the first line
 - *
 - * @param ls the value in points
 - */
 - protected void setFirstLineIndent(float fi) {
 - firstLineIndent = (int) fi;
 - }
 - protected void setPropertiesFromAttributes() {
 - AttributeSet attr = getAttributes();
 - if (attr != null) {
 - setParagraphInsets(attr);
 - setJustification(StyleConstants.getAlignment(attr));
 - lineSpacing = StyleConstants.getLineSpacing(attr);
 - firstLineIndent = (int)StyleConstants.getFirstLineIndent(attr);
 - }
 - }
 - /**
 - * The child views of the paragraph are rows which
 - * have been used to arrange pieces of the Views that
 - * represent the child elements. This is the number
 - * of views that have been tiled in two dimensions,
 - * and should be equivalent to the number of child elements
 - * to the element this view is responsible for.
 - */
 - protected int getLayoutViewCount() {
 - return layoutPool.getViewCount();
 - }
 - /**
 - * The child views of the paragraph are rows which
 - * have been used to arrange pieces of the Views that
 - * represent the child elements. This methods returns
 - * the view responsible for the child element index
 - * (prior to breaking). These are the Views that were
 - * produced from a factory (to represent the child
 - * elements) and used for layout.
 - */
 - protected View getLayoutView(int index) {
 - return layoutPool.getView(index);
 - }
 - /**
 - * 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 r 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(Row r, int desiredSpan, int x) {
 - }
 - /**
 - * Overriden from CompositeView.
 - */
 - protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
 - Shape a, int direction,
 - Position.Bias[] biasRet)
 - throws BadLocationException {
 - int vIndex;
 - if(pos == -1) {
 - vIndex = (direction == NORTH) ?
 - getViewCount() - 1 : 0;
 - }
 - else {
 - if(b == Position.Bias.Backward && pos > 0) {
 - vIndex = getViewIndexAtPosition(pos - 1);
 - }
 - else {
 - vIndex = getViewIndexAtPosition(pos);
 - }
 - if(direction == NORTH) {
 - if(vIndex == 0) {
 - return -1;
 - }
 - vIndex--;
 - }
 - else if(++vIndex >= getViewCount()) {
 - return -1;
 - }
 - }
 - // vIndex gives index of row to look in.
 - JTextComponent text = (JTextComponent)getContainer();
 - Caret c = text.getCaret();
 - Point magicPoint;
 - magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
 - int x;
 - if(magicPoint == null) {
 - Shape posBounds = text.getUI().modelToView(text, pos, b);
 - if(posBounds == null) {
 - x = 0;
 - }
 - else {
 - x = posBounds.getBounds().x;
 - }
 - }
 - else {
 - x = magicPoint.x;
 - }
 - return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
 - }
 - /**
 - * Returns the closest model position to <code>x</code>.
 - * <code>rowIndex</code> gives the index of the view that corresponds
 - * that should be looked in.
 - */
 - // NOTE: This will not properly work if ParagraphView contains
 - // other ParagraphViews. It won't raise, but this does not message
 - // the children views with getNextVisualPositionFrom.
 - protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
 - int direction, Position.Bias[] biasRet,
 - int rowIndex, int x)
 - throws BadLocationException {
 - JTextComponent text = (JTextComponent)getContainer();
 - Document doc = getDocument();
 - AbstractDocument aDoc = (doc instanceof AbstractDocument) ?
 - (AbstractDocument)doc : null;
 - View row = getView(rowIndex);
 - int lastPos = -1;
 - // This could be made better to check backward positions too.
 - biasRet[0] = Position.Bias.Forward;
 - for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
 - View v = row.getView(vc);
 - int start = v.getStartOffset();
 - boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
 - (start, start + 1) : true;
 - if(ltr) {
 - lastPos = start;
 - for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
 - if(text.modelToView(lastPos).getBounds().x >= x) {
 - return lastPos;
 - }
 - }
 - lastPos--;
 - }
 - else {
 - for(lastPos = v.getEndOffset() - 1; lastPos >= start;
 - lastPos--) {
 - if(text.modelToView(lastPos).getBounds().x >= x) {
 - return lastPos;
 - }
 - }
 - lastPos++;
 - }
 - }
 - if(lastPos == -1) {
 - return getStartOffset();
 - }
 - return lastPos;
 - }
 - protected boolean flipEastAndWestAtEnds(int position,
 - Position.Bias bias) {
 - Document doc = getDocument();
 - if(doc instanceof AbstractDocument &&
 - !((AbstractDocument)doc).isLeftToRight(getStartOffset(),
 - getStartOffset() + 1)) {
 - return true;
 - }
 - return false;
 - }
 - // --- FlowView methods ---------------------------------------------
 - /**
 - * Fetch the constraining span to flow against for
 - * the given child index.
 - */
 - public int getFlowSpan(int index) {
 - View child = getView(index);
 - int adjust = 0;
 - if (child instanceof Row) {
 - Row row = (Row) child;
 - adjust = row.getLeftInset() + row.getRightInset();
 - }
 - int span = layoutSpan - adjust;
 - return span;
 - }
 - /**
 - * Fetch the location along the flow axis that the
 - * flow span will start at.
 - */
 - public int getFlowStart(int index) {
 - View child = getView(index);
 - int adjust = 0;
 - if (child instanceof Row) {
 - Row row = (Row) child;
 - adjust = row.getLeftInset();
 - }
 - return tabBase + adjust;
 - }
 - /**
 - * Create a View that should be used to hold a
 - * a rows worth of children in a flow.
 - */
 - protected View createRow() {
 - Element elem = getElement();
 - Row row = new Row(elem);
 - // Adjust for line spacing
 - if(lineSpacing > 1) {
 - float height = row.getPreferredSpan(View.Y_AXIS);
 - float addition = (height * lineSpacing) - height;
 - if(addition > 0) {
 - row.setInsets(row.getTopInset(), row.getLeftInset(),
 - (short) addition, row.getRightInset());
 - }
 - }
 - return row;
 - }
 - // --- TabExpander methods ------------------------------------------
 - /**
 - * Returns the next tab stop position given a reference position.
 - * This view implements the tab coordinate system, and calls
 - * <code>getTabbedSpan</code> on the logical children in the process
 - * of layout to determine the desired span of the children. The
 - * logical children can delegate their tab expansion upward to
 - * the paragraph which knows how to expand tabs.
 - * <code>LabelView</code> is an example of a view that delegates
 - * its tab expansion needs upward to the paragraph.
 - * <p>
 - * This is implemented to try and locate a <code>TabSet</code>
 - * in the paragraph element's attribute set. If one can be
 - * found, its settings will be used, otherwise a default expansion
 - * will be provided. The base location for for tab expansion
 - * is the left inset from the paragraphs most recent allocation
 - * (which is what the layout of the children is based upon).
 - *
 - * @param x the X reference position
 - * @param tabOffset the position within the text stream
 - * that the tab occurred at >= 0.
 - * @return the trailing end of the tab expansion >= 0
 - * @see TabSet
 - * @see TabStop
 - * @see LabelView
 - */
 - public float nextTabStop(float x, int tabOffset) {
 - // If the text isn't left justified, offset by 10 pixels!
 - if(justification != StyleConstants.ALIGN_LEFT)
 - return x + 10.0f;
 - x -= tabBase;
 - TabSet tabs = getTabSet();
 - if(tabs == null) {
 - // a tab every 72 pixels.
 - return (float)(tabBase + (((int)x / 72 + 1) * 72));
 - }
 - TabStop tab = tabs.getTabAfter(x + .01f);
 - if(tab == null) {
 - // no tab, do a default of 5 pixels.
 - // Should this cause a wrapping of the line?
 - return tabBase + x + 5.0f;
 - }
 - int alignment = tab.getAlignment();
 - int offset;
 - switch(alignment) {
 - default:
 - case TabStop.ALIGN_LEFT:
 - // Simple case, left tab.
 - return tabBase + tab.getPosition();
 - case TabStop.ALIGN_BAR:
 - // PENDING: what does this mean?
 - return tabBase + tab.getPosition();
 - case TabStop.ALIGN_RIGHT:
 - case TabStop.ALIGN_CENTER:
 - offset = findOffsetToCharactersInString(tabChars,
 - tabOffset + 1);
 - break;
 - case TabStop.ALIGN_DECIMAL:
 - offset = findOffsetToCharactersInString(tabDecimalChars,
 - tabOffset + 1);
 - break;
 - }
 - if (offset == -1) {
 - offset = getEndOffset();
 - }
 - float charsSize = getPartialSize(tabOffset + 1, offset);
 - switch(alignment) {
 - case TabStop.ALIGN_RIGHT:
 - case TabStop.ALIGN_DECIMAL:
 - // right and decimal are treated the same way, the new
 - // position will be the location of the tab less the
 - // partialSize.
 - return tabBase + Math.max(x, tab.getPosition() - charsSize);
 - case TabStop.ALIGN_CENTER:
 - // Similar to right, but half the partialSize.
 - return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
 - }
 - // will never get here!
 - return x;
 - }
 - /**
 - * Gets the Tabset to be used in calculating tabs.
 - *
 - * @return the TabSet
 - */
 - protected TabSet getTabSet() {
 - return StyleConstants.getTabSet(getElement().getAttributes());
 - }
 - /**
 - * Returns the size used by the views between <code>startOffset</code>
 - * and <code>endOffset</code>. This uses getPartialView to calculate the
 - * size if the child view implements the TabableView interface. If a
 - * size is needed and a View does not implement the TabableView
 - * interface, the preferredSpan will be used.
 - *
 - * @param startOffset the starting document offset >= 0
 - * @param endOffset the ending document offset >= startOffset
 - * @return the size >= 0
 - */
 - protected float getPartialSize(int startOffset, int endOffset) {
 - float size = 0.0f;
 - int viewIndex;
 - int numViews = getViewCount();
 - View view;
 - int viewEnd;
 - int tempEnd;
 - // Have to search layoutPool!
 - // PENDING: when ParagraphView supports breaking location
 - // into layoutPool will have to change!
 - viewIndex = getElement().getElementIndex(startOffset);
 - numViews = layoutPool.getViewCount();
 - while(startOffset < endOffset && viewIndex < numViews) {
 - view = layoutPool.getView(viewIndex++);
 - viewEnd = view.getEndOffset();
 - tempEnd = Math.min(endOffset, viewEnd);
 - if(view instanceof TabableView)
 - size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
 - else if(startOffset == view.getStartOffset() &&
 - tempEnd == view.getEndOffset())
 - size += view.getPreferredSpan(View.X_AXIS);
 - else
 - // PENDING: should we handle this better?
 - return 0.0f;
 - startOffset = viewEnd;
 - }
 - return size;
 - }
 - /**
 - * Finds the next character in the document with a character in
 - * <code>string</code>, starting at offset <code>start</code>. If
 - * there are no characters found, -1 will be returned.
 - *
 - * @param string the string of characters
 - * @param start where to start in the model >= 0
 - * @return the document offset or -1
 - */
 - protected int findOffsetToCharactersInString(char[] string,
 - int start) {
 - int stringLength = string.length;
 - int end = getEndOffset();
 - Segment seg = new Segment();
 - try {
 - getDocument().getText(start, end - start, seg);
 - } catch (BadLocationException ble) {
 - return -1;
 - }
 - for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
 - counter < maxCounter; counter++) {
 - char currentChar = seg.array[counter];
 - for(int subCounter = 0; subCounter < stringLength;
 - subCounter++) {
 - if(currentChar == string[subCounter])
 - return counter - seg.offset + start;
 - }
 - }
 - // No match.
 - return -1;
 - }
 - /**
 - * @return where tabs are calculated from.
 - */
 - protected float getTabBase() {
 - return (float)tabBase;
 - }
 - // ---- View methods ----------------------------------------------------
 - /**
 - * Renders using the given rendering surface and area on that
 - * surface. This is implemented to delgate to the superclass
 - * after stashing the base coordinate for tab calculations.
 - *
 - * @param g the rendering surface to use
 - * @param a the allocated region to render into
 - * @see View#paint
 - */
 - public void paint(Graphics g, Shape a) {
 - Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 - tabBase = alloc.x + getLeftInset();
 - super.paint(g, a);
 - }
 - /**
 - * Determines the desired alignment for this view along an
 - * axis. This is implemented to give the alignment to the
 - * center of the first row along the y axis, and the default
 - * along the x axis.
 - *
 - * @param axis may be either View.X_AXIS or View.Y_AXIS
 - * @returns the desired alignment. This should be a value
 - * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
 - * origin and 1.0 indicates alignment to the full span
 - * away from the origin. An alignment of 0.5 would be the
 - * center of the view.
 - */
 - public float getAlignment(int axis) {
 - switch (axis) {
 - case Y_AXIS:
 - float a = 0.5f;
 - if (getViewCount() != 0) {
 - int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
 - View v = getView(0);
 - int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
 - a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
 - }
 - return a;
 - case X_AXIS:
 - return 0.5f;
 - default:
 - throw new IllegalArgumentException("Invalid axis: " + axis);
 - }
 - }
 - /**
 - * Breaks this view on the given axis at the given length.<p>
 - * ParagraphView instances are breakable along the Y_AXIS only, and only if
 - * <code>len</code> is after the first line.
 - *
 - * @param axis may be either View.X_AXIS or View.Y_AXIS
 - * @param len specifies where a potential break is desired
 - * along the given axis >= 0
 - * @param a the current allocation of the view
 - * @return the fragment of the view that represents the
 - * given span, if the view can be broken. If the view
 - * doesn't support breaking behavior, the view itself is
 - * returned.
 - * @see View#breakView
 - */
 - public View breakView(int axis, float len, Shape a) {
 - if(axis == View.Y_AXIS) {
 - if(a != null) {
 - Rectangle alloc = a.getBounds();
 - setSize(alloc.width, alloc.height);
 - }
 - // Determine what row to break on.
 - // PENDING(prinz) add break support
 - return this;
 - }
 - return this;
 - }
 - /**
 - * Gets the break weight for a given location.
 - * ParagraphView instances are breakable along the Y_AXIS only, and
 - * only if <code>len</code> is after the first row. If the length
 - * is less than one row, a value of BadBreakWeight is returned.
 - *
 - * @param axis may be either View.X_AXIS or View.Y_AXIS
 - * @param len specifies where a potential break is desired >= 0
 - * @return a value indicating the attractiveness of breaking here
 - * @see View#getBreakWeight
 - */
 - public int getBreakWeight(int axis, float len) {
 - if(axis == View.Y_AXIS) {
 - // PENDING(prinz) make this return a reasonable value
 - // when paragraph breaking support is re-implemented.
 - // If less than one row, bad weight value should be
 - // returned.
 - //return GoodBreakWeight;
 - return BadBreakWeight;
 - }
 - return BadBreakWeight;
 - }
 - /**
 - * Gives notification from the document that attributes were changed
 - * in a location that this view is responsible for.
 - *
 - * @param changes 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 View#changedUpdate
 - */
 - public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
 - // update any property settings stored, and layout should be
 - // recomputed
 - setPropertiesFromAttributes();
 - layoutChanged(X_AXIS);
 - layoutChanged(Y_AXIS);
 - super.changedUpdate(changes, a, f);
 - }
 - // --- variables -----------------------------------------------
 - private int justification;
 - private float lineSpacing;
 - /** Indentation for the first line, from the left inset. */
 - protected int firstLineIndent;
 - /**
 - * Used by the TabExpander functionality to determine
 - * where to base the tab calculations. This is basically
 - * the location of the left side of the paragraph.
 - */
 - private int tabBase;
 - /**
 - * Used to create an i18n-based layout strategy
 - */
 - static Class i18nStrategy;
 - /** Used for searching for a tab. */
 - static char[] tabChars;
 - /** Used for searching for a tab or decimal character. */
 - static char[] tabDecimalChars;
 - static {
 - tabChars = new char[1];
 - tabChars[0] = '\t';
 - tabDecimalChars = new char[2];
 - tabDecimalChars[0] = '\t';
 - tabDecimalChars[1] = '.';
 - }
 - /**
 - * Internally created view that has the purpose of holding
 - * the views that represent the children of the paragraph
 - * that have been arranged in rows.
 - */
 - class Row extends BoxView {
 - Row(Element elem) {
 - super(elem, View.X_AXIS);
 - }
 - /**
 - * This is reimplemented to do nothing since the
 - * paragraph fills in the row with its needed
 - * children.
 - */
 - protected void loadChildren(ViewFactory f) {
 - }
 - /**
 - * Fetches the attributes to use when rendering. This view
 - * isn't directly responsible for an element so it returns
 - * the outer classes attributes.
 - */
 - public AttributeSet getAttributes() {
 - View p = getParent();
 - return (p != null) ? p.getAttributes() : null;
 - }
 - public float getAlignment(int axis) {
 - if (axis == View.X_AXIS) {
 - switch (justification) {
 - case StyleConstants.ALIGN_LEFT:
 - return 0;
 - case StyleConstants.ALIGN_RIGHT:
 - return 1;
 - case StyleConstants.ALIGN_CENTER:
 - case StyleConstants.ALIGN_JUSTIFIED:
 - return 0.5f;
 - }
 - }
 - return super.getAlignment(axis);
 - }
 - /**
 - * Provides a mapping from the document model coordinate space
 - * to the coordinate space of the view mapped to it. This is
 - * implemented to let the superclass find the position along
 - * the major axis and the allocation of the row is used
 - * along the minor axis, so that even though the children
 - * are different heights they all get the same caret height.
 - *
 - * @param pos the position to convert
 - * @param a the allocated region to render into
 - * @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 {
 - Rectangle r = a.getBounds();
 - View v = getViewAtPosition(pos, r);
 - if ((v != null) && (!v.getElement().isLeaf())) {
 - // Don't adjust the height if the view represents a branch.
 - return super.modelToView(pos, a, b);
 - }
 - r = a.getBounds();
 - int height = r.height;
 - int y = r.y;
 - Shape loc = super.modelToView(pos, a, b);
 - r = loc.getBounds();
 - r.height = height;
 - r.y = y;
 - return r;
 - }
 - /**
 - * Range represented by a row in the paragraph is only
 - * a subset of the total range of the paragraph element.
 - * @see View#getRange
 - */
 - public int getStartOffset() {
 - int offs = Integer.MAX_VALUE;
 - int n = getViewCount();
 - for (int i = 0; i < n; i++) {
 - View v = getView(i);
 - offs = Math.min(offs, v.getStartOffset());
 - }
 - return offs;
 - }
 - public int getEndOffset() {
 - int offs = 0;
 - int n = getViewCount();
 - for (int i = 0; i < n; i++) {
 - View v = getView(i);
 - offs = Math.max(offs, v.getEndOffset());
 - }
 - return offs;
 - }
 - /**
 - * Perform layout for the minor axis of the box (i.e. the
 - * axis orthoginal to the axis that it represents). The results
 - * of the layout should be placed in the given arrays which represent
 - * the allocations to the children along the minor axis.
 - * <p>
 - * This is implemented to do a baseline layout of the children
 - * by calling BoxView.baselineLayout.
 - *
 - * @param targetSpan the total span given to the view, which
 - * whould be used to layout the children.
 - * @param axis the axis being layed out.
 - * @param offsets the offsets from the origin of the view for
 - * each of the child views. This is a return value and is
 - * filled in by the implementation of this method.
 - * @param spans the span of each child view. This is a return
 - * value and is filled in by the implementation of this method.
 - * @returns the offset and span for each child view in the
 - * offsets and spans parameters.
 - */
 - protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
 - baselineLayout(targetSpan, axis, offsets, spans);
 - }
 - protected SizeRequirements calculateMinorAxisRequirements(int axis,
 - SizeRequirements r) {
 - return baselineRequirements(axis, r);
 - }
 - /**
 - * Fetches the child view index representing the given position in
 - * the model.
 - *
 - * @param pos the position >= 0
 - * @returns index of the view representing the given position, or
 - * -1 if no view represents that position
 - */
 - protected int getViewIndexAtPosition(int pos) {
 - // This is expensive, but are views are not necessarily layed
 - // out in model order.
 - if(pos < getStartOffset() || pos >= getEndOffset())
 - return -1;
 - for(int counter = getViewCount() - 1; counter >= 0; counter--) {
 - View v = getView(counter);
 - if(pos >= v.getStartOffset() &&
 - pos < v.getEndOffset()) {
 - return counter;
 - }
 - }
 - return -1;
 - }
 - }
 - }