| /******************************************************************************* |
| * <copyright> |
| * |
| * Copyright (c) 2005, 2013 SAP AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SAP AG - initial API, implementation and documentation |
| * mwenz - Bug 415884 - Cannot query size of a multi-line text |
| * |
| * </copyright> |
| * |
| *******************************************************************************/ |
| package org.eclipse.graphiti.ui.internal.services.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.draw2d.ExclusionSearch; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.TextUtilities; |
| import org.eclipse.draw2d.geometry.Dimension; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.EditPartViewer; |
| import org.eclipse.gef.GraphicalEditPart; |
| import org.eclipse.gef.LayerConstants; |
| import org.eclipse.gef.editparts.ScalableFreeformRootEditPart; |
| import org.eclipse.graphiti.datatypes.IDimension; |
| import org.eclipse.graphiti.datatypes.ILocation; |
| import org.eclipse.graphiti.internal.datatypes.impl.DimensionImpl; |
| import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm; |
| import org.eclipse.graphiti.mm.algorithms.styles.Font; |
| import org.eclipse.graphiti.mm.pictograms.Anchor; |
| import org.eclipse.graphiti.mm.pictograms.AnchorContainer; |
| import org.eclipse.graphiti.mm.pictograms.ChopboxAnchor; |
| import org.eclipse.graphiti.mm.pictograms.Connection; |
| import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator; |
| import org.eclipse.graphiti.mm.pictograms.FreeFormConnection; |
| import org.eclipse.graphiti.mm.pictograms.Shape; |
| import org.eclipse.graphiti.services.Graphiti; |
| import org.eclipse.graphiti.services.ILayoutService; |
| import org.eclipse.graphiti.ui.internal.services.IGefService; |
| import org.eclipse.graphiti.ui.internal.util.DataTypeTransformation; |
| import org.eclipse.graphiti.ui.internal.util.gef.ScalableRootEditPartAnimated; |
| |
| /** |
| * A collection of static helper methods regarding GEF. |
| * |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class GefService implements IGefService { |
| |
| protected static final String PE = "*PE* "; //$NON-NLS-1$ |
| |
| public void selectEditPart(EditPartViewer viewer, Object modelObject) { |
| if (modelObject == null) |
| return; |
| |
| viewer.getControl().forceFocus(); |
| Object editpart = viewer.getEditPartRegistry().get(modelObject); |
| if (editpart instanceof EditPart) { |
| viewer.flush(); |
| viewer.select((EditPart) editpart); |
| } |
| } |
| |
| public Point calculateTranslation(EditPart source, EditPart target) { |
| if (!(source instanceof GraphicalEditPart && target instanceof GraphicalEditPart)) |
| throw new RuntimeException("Both EditParts must be an instance of GraphicalEditPart: " + source + " " + target); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| Point result = new Point(0, 0); |
| if (source == target) // quick-check |
| return result; |
| |
| ((GraphicalEditPart) source).getContentPane().translateToAbsolute(result); |
| ((GraphicalEditPart) target).getContentPane().translateToRelative(result); |
| return result; |
| } |
| |
| public Object getLayoutConstraint(EditPart editPart) { |
| if (editPart instanceof GraphicalEditPart) { |
| IFigure childFigure = ((GraphicalEditPart) editPart).getFigure(); |
| if (childFigure.getParent() != null && childFigure.getParent().getLayoutManager() != null) { // should |
| // always be |
| // true |
| // ask the parent for the current constraints |
| Object constraint = childFigure.getParent().getLayoutManager().getConstraint(childFigure); |
| return constraint; |
| } |
| } |
| return null; |
| } |
| |
| public EditPart findEditPartAt(EditPartViewer viewer, Point location, boolean includeConnections) { |
| |
| EditPart editPart = findObjectAt(viewer, location, includeConnections); |
| |
| if (editPart instanceof ScalableRootEditPartAnimated) { |
| List<EditPart> children = getEditPartChildren(editPart); |
| if (children.size() > 0) { |
| editPart = children.get(0); |
| } |
| } |
| return editPart; |
| } |
| |
| private EditPart findObjectAt(final EditPartViewer viewer, Point pt, boolean includeConnections) { |
| class ConditionalTreeSearch extends ExclusionSearch { |
| ConditionalTreeSearch(Collection<?> coll) { |
| super(coll); |
| } |
| |
| @Override |
| public boolean accept(IFigure figure) { |
| EditPart editpart = null; |
| while (editpart == null && figure != null) { |
| editpart = (EditPart) viewer.getVisualPartMap().get(figure); |
| figure = figure.getParent(); |
| } |
| return editpart != null; |
| } |
| } |
| |
| ScalableFreeformRootEditPart rootEditPart = (ScalableFreeformRootEditPart) viewer.getRootEditPart(); |
| IFigure figure = null; |
| |
| if (includeConnections) { |
| IFigure connectionLayer = rootEditPart.getLayer(LayerConstants.CONNECTION_LAYER); |
| figure = connectionLayer.findFigureAt(pt.x, pt.y, new ConditionalTreeSearch(Collections.EMPTY_LIST)); |
| } |
| // if figure not searched or not found on connection layer try to find a |
| // figure on the primary layer |
| if (figure == null) { |
| IFigure primaryLayer = rootEditPart.getLayer(LayerConstants.PRIMARY_LAYER); |
| figure = primaryLayer.findFigureAt(pt.x, pt.y, new ConditionalTreeSearch(Collections.EMPTY_LIST)); |
| } |
| |
| EditPart part = null; |
| while (part == null && figure != null) { |
| part = (EditPart) viewer.getVisualPartMap().get(figure); |
| figure = figure.getParent(); |
| } |
| if (part == null) |
| return viewer.getContents(); |
| return part; |
| } |
| |
| public List<EditPart> getConnectionsContainedInEditPart(EditPart ep) { |
| // Compute connections whose start and target are somewhere in the |
| // editpart sub hierarchy of ep |
| List<EditPart> childEditParts = getAllEditPartChildren(ep); |
| List<Connection> sourceConnections = new ArrayList<Connection>(); |
| List<Connection> targetConnections = new ArrayList<Connection>(); |
| for (EditPart cep : childEditParts) { |
| if (cep.getModel() instanceof AnchorContainer) { |
| AnchorContainer anchorContainer = (AnchorContainer) cep.getModel(); |
| EList<Anchor> anchors = anchorContainer.getAnchors(); |
| for (Anchor anchor : anchors) { |
| sourceConnections.addAll(anchor.getIncomingConnections()); |
| targetConnections.addAll(anchor.getOutgoingConnections()); |
| } |
| } |
| } |
| sourceConnections.retainAll(targetConnections); |
| |
| // Extract edit parts and decorators from the connections |
| List<EditPart> result = new ArrayList<EditPart>(); |
| for (Connection connection : sourceConnections) { |
| Object connectionEditPart = ep.getRoot().getViewer().getEditPartRegistry().get(connection); |
| result.add((EditPart) connectionEditPart); |
| EList<ConnectionDecorator> connectionDecorators = connection.getConnectionDecorators(); |
| for (ConnectionDecorator decorator : connectionDecorators) { |
| Object decoratorEditPart = ep.getRoot().getViewer().getEditPartRegistry().get(decorator); |
| result.add((EditPart) decoratorEditPart); |
| } |
| } |
| return result; |
| |
| } |
| |
| private List<EditPart> getAllEditPartChildren(EditPart ep) { |
| List<EditPart> res = new ArrayList<EditPart>(); |
| List<EditPart> children = getEditPartChildren(ep); |
| for (EditPart editPart : children) { |
| res.add(editPart); |
| res.addAll(getAllEditPartChildren(editPart)); |
| } |
| return res; |
| } |
| |
| public Point getConnectionPointAt(Connection c, double d) { |
| Point ret = null; |
| |
| Anchor startAnchor = c.getStart(); |
| final ILayoutService layoutService = Graphiti.getLayoutService(); |
| ILocation startLocation = layoutService.getLocationRelativeToDiagram(startAnchor); |
| Point startPoint = new Point(startLocation.getX(), startLocation.getY()); |
| |
| Anchor endAnchor = c.getEnd(); |
| ILocation endLocation = layoutService.getLocationRelativeToDiagram(endAnchor); |
| Point endPoint = new Point(endLocation.getX(), endLocation.getY()); |
| |
| // special solutions for chopbox anchors |
| if (startAnchor instanceof ChopboxAnchor || endAnchor instanceof ChopboxAnchor) { |
| if (startAnchor instanceof ChopboxAnchor) { |
| ChopboxAnchor cbStartAnchor = (ChopboxAnchor) startAnchor; |
| GraphicsAlgorithm parentGa = cbStartAnchor.getParent().getGraphicsAlgorithm(); |
| Shape shape = (Shape) cbStartAnchor.getParent(); |
| ILocation location = layoutService.getLocationRelativeToDiagram(shape); |
| Rectangle parentRect = new Rectangle(location.getX(), location.getY(), parentGa.getWidth(), parentGa.getHeight()); |
| |
| Point pointNextToStartAnchor = endPoint.getCopy(); |
| |
| if (c instanceof FreeFormConnection) { |
| FreeFormConnection ffc = (FreeFormConnection) c; |
| List<org.eclipse.graphiti.mm.algorithms.styles.Point> bendpoints = ffc.getBendpoints(); |
| if (!bendpoints.isEmpty()) { |
| org.eclipse.graphiti.mm.algorithms.styles.Point firstBendpoint = bendpoints.get(0); |
| pointNextToStartAnchor.setLocation(firstBendpoint.getX(), firstBendpoint.getY()); |
| } |
| } |
| |
| Point chopboxLocationOnBox = getChopboxLocationOnBox(pointNextToStartAnchor, parentRect); |
| startPoint.setLocation(chopboxLocationOnBox); |
| } |
| |
| if (endAnchor instanceof ChopboxAnchor) { |
| ChopboxAnchor cbEndAnchor = (ChopboxAnchor) endAnchor; |
| GraphicsAlgorithm parentGa = cbEndAnchor.getParent().getGraphicsAlgorithm(); |
| Shape shape = (Shape) cbEndAnchor.getParent(); |
| ILocation location = layoutService.getLocationRelativeToDiagram(shape); |
| Rectangle parentRect = new Rectangle(location.getX(), location.getY(), parentGa.getWidth(), parentGa.getHeight()); |
| |
| Point pointNextToEndAnchor = startPoint.getCopy(); |
| |
| if (c instanceof FreeFormConnection) { |
| FreeFormConnection ffc = (FreeFormConnection) c; |
| List<org.eclipse.graphiti.mm.algorithms.styles.Point> bendpoints = ffc.getBendpoints(); |
| if (!bendpoints.isEmpty()) { |
| org.eclipse.graphiti.mm.algorithms.styles.Point lastBendpoint = bendpoints.get(bendpoints.size() - 1); |
| pointNextToEndAnchor.setLocation(lastBendpoint.getX(), lastBendpoint.getY()); |
| } |
| } |
| |
| Point chopboxLocationOnBox = getChopboxLocationOnBox(pointNextToEndAnchor, parentRect); |
| endPoint.setLocation(chopboxLocationOnBox); |
| } |
| |
| } |
| |
| if (c instanceof FreeFormConnection) { |
| FreeFormConnection ffc = (FreeFormConnection) c; |
| List<org.eclipse.graphiti.mm.algorithms.styles.Point> bendpoints = ffc.getBendpoints(); |
| Point[] draw2dPoints = new Point[bendpoints.size() + 2]; |
| { |
| draw2dPoints[0] = startPoint; |
| int i = 1; |
| for (org.eclipse.graphiti.mm.algorithms.styles.Point pictogramsPoint : bendpoints) { |
| draw2dPoints[i] = new Point(pictogramsPoint.getX(), pictogramsPoint.getY()); |
| i++; |
| } |
| draw2dPoints[i] = endPoint; |
| } |
| |
| double completeDistance = getDistance(draw2dPoints); |
| double absDistanceToRelPoint = completeDistance * d; |
| |
| double distanceSum = 0; |
| for (int i = 0; i < draw2dPoints.length - 1; i++) { |
| double oldDistanceSum = distanceSum; |
| Point currentPoint = draw2dPoints[i]; |
| Point nextPoint = draw2dPoints[i + 1]; |
| // no need to check both points |
| // actually this would lead to division-by-zero in the following |
| // calculation |
| if (currentPoint.equals(nextPoint)) |
| continue; |
| |
| double additionalDistanceToNext = getDistance(currentPoint, nextPoint); |
| distanceSum += additionalDistanceToNext; |
| if (distanceSum >= absDistanceToRelPoint) { |
| double thisRelative = ((completeDistance * d) - oldDistanceSum) / additionalDistanceToNext; |
| ret = getPointAt(currentPoint, nextPoint, thisRelative); |
| break; // or return ret; |
| } |
| } |
| |
| } else { |
| ret = getPointAt(startPoint.x, startPoint.y, endPoint.x, endPoint.y, d); |
| } |
| return ret; |
| } |
| |
| public Point getChopboxLocationOnBox(Point reference, Rectangle box) { |
| Rectangle r = Rectangle.SINGLETON; |
| r.setBounds(box); |
| r.translate(-1, -1); |
| r.resize(1, 1); |
| |
| // getOwner().translateToAbsolute(r); |
| float centerX = r.x + 0.5f * r.width; |
| float centerY = r.y + 0.5f * r.height; |
| |
| // CHANGED: returning the center in case of a divide-by-zero is not a |
| // good result |
| // if (r.isEmpty() || (reference.x == (int)centerX && reference.y == |
| // (int)centerY)) |
| // return new Point((int)centerX, (int)centerY); //This avoids |
| // divide-by-zero |
| |
| float dx = reference.x - centerX; |
| float dy = reference.y - centerY; |
| |
| // r.width, r.height, dx, and dy are guaranteed to be non-zero. |
| |
| // CHANGED: in case of "nearly zero" (divide-by-zero or |
| // rounding-problems) would happen. |
| // Instead return a point on the border of the figure. |
| // Doesn't matter which one, because it is directly in the center, so |
| // take top-middle. |
| float max = Math.max(Math.abs(dx) / r.width, Math.abs(dy) / r.height); |
| if (max <= 0.001f) { |
| return new Point((int) centerX, r.y); |
| } |
| |
| float scale = 0.5f / max; |
| |
| dx *= scale; |
| dy *= scale; |
| centerX += dx; |
| centerY += dy; |
| |
| return new Point(Math.round(centerX), Math.round(centerY)); |
| } |
| |
| public Point getAbsolutePointOnConnection(Connection c, double distance) { |
| Point ret = null; |
| |
| Anchor startAnchor = c.getStart(); |
| ILocation startLocation = Graphiti.getLayoutService().getLocationRelativeToDiagram(startAnchor); |
| Point startPoint = new Point(startLocation.getX(), startLocation.getY()); |
| |
| Anchor endAnchor = c.getEnd(); |
| ILocation endLocation = Graphiti.getLayoutService().getLocationRelativeToDiagram(endAnchor); |
| Point endPoint = new Point(endLocation.getX(), endLocation.getY()); |
| |
| // special solutions for chopbox anchors |
| if (startAnchor instanceof ChopboxAnchor || endAnchor instanceof ChopboxAnchor) { |
| if (startAnchor instanceof ChopboxAnchor) { |
| ChopboxAnchor cbStartAnchor = (ChopboxAnchor) startAnchor; |
| GraphicsAlgorithm parentGa = cbStartAnchor.getParent().getGraphicsAlgorithm(); |
| Rectangle parentRect = new Rectangle(parentGa.getX(), parentGa.getY(), parentGa.getWidth(), parentGa.getHeight()); |
| |
| Point pointNextToStartAnchor = endPoint.getCopy(); |
| |
| if (c instanceof FreeFormConnection) { |
| FreeFormConnection ffc = (FreeFormConnection) c; |
| List<org.eclipse.graphiti.mm.algorithms.styles.Point> bendpoints = ffc.getBendpoints(); |
| if (!bendpoints.isEmpty()) { |
| org.eclipse.graphiti.mm.algorithms.styles.Point firstBendpoint = bendpoints.get(0); |
| pointNextToStartAnchor.setLocation(firstBendpoint.getX(), firstBendpoint.getY()); |
| } |
| } |
| |
| Point chopboxLocationOnBox = getChopboxLocationOnBox(pointNextToStartAnchor, parentRect); |
| startPoint.setLocation(chopboxLocationOnBox); |
| } |
| |
| if (endAnchor instanceof ChopboxAnchor) { |
| ChopboxAnchor cbEndAnchor = (ChopboxAnchor) endAnchor; |
| GraphicsAlgorithm parentGa = cbEndAnchor.getParent().getGraphicsAlgorithm(); |
| Rectangle parentRect = new Rectangle(parentGa.getX(), parentGa.getY(), parentGa.getWidth(), parentGa.getHeight()); |
| |
| Point pointNextToEndAnchor = startPoint.getCopy(); |
| |
| if (c instanceof FreeFormConnection) { |
| FreeFormConnection ffc = (FreeFormConnection) c; |
| List<org.eclipse.graphiti.mm.algorithms.styles.Point> bendpoints = ffc.getBendpoints(); |
| if (!bendpoints.isEmpty()) { |
| org.eclipse.graphiti.mm.algorithms.styles.Point lastBendpoint = bendpoints.get(bendpoints.size() - 1); |
| pointNextToEndAnchor.setLocation(lastBendpoint.getX(), lastBendpoint.getY()); |
| } |
| } |
| |
| Point chopboxLocationOnBox = getChopboxLocationOnBox(pointNextToEndAnchor, parentRect); |
| endPoint.setLocation(chopboxLocationOnBox); |
| } |
| |
| } |
| |
| if (c instanceof FreeFormConnection) { |
| FreeFormConnection ffc = (FreeFormConnection) c; |
| List<org.eclipse.graphiti.mm.algorithms.styles.Point> bendpoints = ffc.getBendpoints(); |
| Point[] draw2dPoints = new Point[bendpoints.size() + 2]; |
| { |
| draw2dPoints[0] = startPoint; |
| int i = 1; |
| for (org.eclipse.graphiti.mm.algorithms.styles.Point pictogramsPoint : bendpoints) { |
| draw2dPoints[i] = new Point(pictogramsPoint.getX(), pictogramsPoint.getY()); |
| i++; |
| } |
| draw2dPoints[i] = endPoint; |
| } |
| |
| if (distance < 0) { |
| // mirror the array |
| mirrorArray(draw2dPoints); |
| } |
| |
| double absDistance = Math.abs(distance); |
| |
| double distanceSum = 0; |
| for (int i = 0; i < draw2dPoints.length - 1; i++) { |
| Point currentPoint = draw2dPoints[i]; |
| Point nextPoint = draw2dPoints[i + 1]; |
| double additionalDistanceToNext = getDistance(currentPoint, nextPoint); |
| distanceSum += additionalDistanceToNext; |
| if (distanceSum >= absDistance) { |
| // double thisDistance = (distance - oldDistanceSum) / |
| // additionalDistanceToNext; |
| // ret = getMidpoint(currentPoint, nextPoint, thisDistance); |
| ret = getDistantPoint(currentPoint, nextPoint, absDistance); |
| break; // or return ret; |
| } |
| } |
| |
| } else { |
| ret = getDistantPoint(startLocation.getX(), startLocation.getY(), endLocation.getX(), endLocation.getY(), distance); |
| } |
| |
| if (ret == null) { |
| ret = startPoint; |
| } |
| |
| return ret; |
| } |
| |
| public Point getDistantPoint(Point start, Point end, double distance) { |
| Point ret = getDistantPoint(start.x, start.y, end.x, end.y, distance); |
| return ret; |
| } |
| |
| public Point getDistantPoint(int startX, int startY, int endX, int endY, double distance) { |
| Point ret; |
| double completeDistance = Math.sqrt(((endX - startX) * (endX - startX)) + ((endY - startY) * (endY - startY))); |
| double relative; |
| if (distance >= 0) { |
| relative = (distance > completeDistance) ? 1 : distance / completeDistance; |
| ret = getPointAt(startX, startY, endX, endY, relative); |
| } else { |
| relative = (-distance > completeDistance) ? 0 : 1 - (distance / completeDistance); |
| ret = getPointAt(startX, startY, endX, endY, relative); |
| } |
| |
| return ret; |
| } |
| |
| public Point getPointAt(int startX, int startY, int endX, int endY, double d) { |
| Point ret; |
| int midX = (int) Math.round((startX + d * (endX - startX))); |
| int midY = (int) Math.round((startY + d * (endY - startY))); |
| ret = new Point(midX, midY); |
| return ret; |
| } |
| |
| public Point getPointAt(Point start, Point end, double d) { |
| Point ret = getPointAt(start.x, start.y, end.x, end.y, d); |
| return ret; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.graphiti.ui.internal.util.gef.IGefService#getDistance(org |
| * .eclipse.draw2d.geometry.Point[]) |
| */ |
| public double getDistance(Point[] points) { |
| double ret = 0; |
| for (int i = 0; i < points.length - 1; i++) { |
| Point currentPoint = points[i]; |
| Point nextPoint = points[i + 1]; |
| ret += getDistance(currentPoint, nextPoint); |
| } |
| return ret; |
| } |
| |
| private double getDistance(Point start, Point end) { |
| int xDist = end.x - start.x; |
| int yDist = end.y - start.y; |
| double ret = Math.sqrt((xDist * xDist) + (yDist * yDist)); |
| return ret; |
| } |
| |
| public IDimension calculateTextSize(String text, Font font) { |
| return calculateTextSize(text, font, false); |
| } |
| |
| public IDimension calculateTextSize(String text, Font font, boolean handleMultiline) { |
| IDimension dimension = null; |
| if (text == null || font == null || font.getName() == null) { |
| return dimension; |
| } |
| |
| org.eclipse.swt.graphics.Font swtFont = DataTypeTransformation.toSwtFont(font); |
| if (swtFont != null) { |
| Dimension se; |
| if (handleMultiline) { |
| se = TextUtilities.INSTANCE.getTextExtents(text, swtFont); |
| } else { |
| se = TextUtilities.INSTANCE.getStringExtents(text, swtFont); |
| } |
| |
| if (se != null) { |
| dimension = new DimensionImpl(se.width, se.height); |
| } |
| if (!swtFont.isDisposed()) { |
| swtFont.dispose(); |
| } |
| } |
| return dimension; |
| } |
| |
| public void mirrorArray(Point[] draw2dPoints) { |
| int length = draw2dPoints.length; |
| for (int i = 0; i < length / 2; i++) { |
| Point pTemp = draw2dPoints[length - 1 - i]; |
| draw2dPoints[length - 1 - i] = draw2dPoints[i]; |
| draw2dPoints[i] = pTemp; |
| } |
| } |
| |
| public List<EditPart> getEditPartChildren(EditPart editPart) { |
| @SuppressWarnings("unchecked") |
| List<EditPart> ret = editPart.getChildren(); |
| return ret; |
| } |
| |
| public List<GraphicalEditPart> getSourceConnections(GraphicalEditPart graphicalEditPart) { |
| @SuppressWarnings("unchecked") |
| List<GraphicalEditPart> sourceConnections = graphicalEditPart.getSourceConnections(); |
| return sourceConnections; |
| } |
| |
| public List<GraphicalEditPart> getTargetConnections(GraphicalEditPart graphicalEditPart) { |
| @SuppressWarnings("unchecked") |
| List<GraphicalEditPart> targetConnections = graphicalEditPart.getTargetConnections(); |
| return targetConnections; |
| } |
| |
| public List<EditPart> getSelectedEditParts(EditPartViewer editPartViewer) { |
| @SuppressWarnings("unchecked") |
| List<EditPart> selectedEditParts = editPartViewer.getSelectedEditParts(); |
| return selectedEditParts; |
| } |
| } |