package ij.plugin.frame;
import ij.*;
import ij.gui.*;
import ij.measure.Calibration;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
public class SyncWindows extends PlugInFrame implements
ActionListener, MouseMotionListener, MouseListener, DisplayChangeListener,
ItemListener, ImageListener, CommandListener {
protected Vector vwins = null;
protected int oldX, oldY;
protected int x=0;
protected int y=0;
protected java.awt.List wList;
protected java.awt.Panel panel;
protected Checkbox cCursor, cSlice, cChannel, cFrame, cCoords, cScaling;
protected Button bSyncAll, bUnsyncAll;
protected Vector vListMap;
protected final ImageJ ijInstance;
private double currentMag = 1;
private Rectangle currentSrcRect = new Rectangle(0,0,400,400);
static final int RSZ = 16;
static final int SZ = RSZ/2;
static final int SCALE = 3;
private static SyncWindows instance;
private static Point location;
public SyncWindows() {
this("Synchronize Windows");
}
public SyncWindows(String s) {
super(s);
ijInstance = IJ.getInstance();
if (instance!=null) {
WindowManager.toFront(instance);
return;
}
instance = this;
panel = controlPanel();
add(panel);
GUI.scale(this);
pack();
setResizable(false);
IJ.register(this.getClass());
if (location==null)
location = getLocation();
else
setLocation(location);
updateWindowList();
WindowManager.addWindow(this);
ImagePlus.addImageListener(this);
Executer.addCommandListener(this);
show();
}
public static void setC(ImageWindow source, int channel) {
SyncWindows syncWindows = instance;
if (syncWindows==null || !syncWindows.synced(source))
return;
DisplayChangeEvent event=new DisplayChangeEvent(source, DisplayChangeEvent.CHANNEL, channel);
syncWindows.displayChanged(event);
}
public static void setZ(ImageWindow source, int slice) {
SyncWindows syncWindows = instance;
if (syncWindows==null || !syncWindows.synced(source))
return;
DisplayChangeEvent event=new DisplayChangeEvent(source, DisplayChangeEvent.Z, slice);
syncWindows.displayChanged(event);
}
public static void setT(ImageWindow source, int frame) {
SyncWindows syncWindows = instance;
if (syncWindows==null || !syncWindows.synced(source))
return;
DisplayChangeEvent event = new DisplayChangeEvent(source, DisplayChangeEvent.T, frame);
syncWindows.displayChanged(event);
}
private boolean synced(ImageWindow source) {
if (source==null || vwins==null)
return false;
ImagePlus imp = source.getImagePlus();
if (imp==null)
return false;
return vwins.contains(Integer.valueOf(imp.getID()));
}
public void displayChanged(DisplayChangeEvent e) {
if (vwins == null) return;
Object source = e.getSource();
int type = e.getType();
int value = e.getValue();
ImagePlus imp;
ImageWindow iw;
ImageWindow iwc = WindowManager.getCurrentImage().getWindow();
if (!iwc.equals(source)) return;
if (cChannel.getState() && type==DisplayChangeEvent.CHANNEL) {
for (int n=0; n<vwins.size();++n) {
imp = getImageFromVector(n);
if (imp!=null) {
iw = imp.getWindow();
if (!iw.equals(source))
imp.setC(value);
}
}
}
if (cSlice.getState() && type==DisplayChangeEvent.Z) {
for (int n=0; n<vwins.size();++n) {
imp = getImageFromVector(n);
if (imp!=null) {
iw = imp.getWindow();
if (!iw.equals(source)) {
if (imp.getNSlices()==1 && imp.getNFrames()>1)
imp.setT(value);
else
imp.setZ(value);
}
}
}
}
if (cFrame.getState() && type==DisplayChangeEvent.T) {
for(int n=0; n<vwins.size();++n) {
imp = getImageFromVector(n);
if (imp!=null) {
iw = imp.getWindow();
if (!iw.equals(source))
imp.setT(value);
}
}
}
ImageCanvas icc = iwc.getCanvas();
storeCanvasState(icc);
}
public void mouseMoved(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
Point p;
Point oldp;
oldX = x; oldY = y;
x = e.getX();
y = e.getY();
p = new Point(x, y);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
ic = iw.getCanvas();
if (cCoords.getState() && iw != iwc) {
p = getMatchingCoords(ic, icc, x, y);
oldp = getMatchingCoords(ic, icc, oldX, oldY);
} else {
p.x = x;
p.y = y;
}
Roi roi = imp.getRoi();
if (! (roi != null && roi instanceof PolygonRoi && roi.getState() == Roi.CONSTRUCTING) )
drawSyncCursor(imp, p.x, p.y);
if (iw != iwc)
ic.mouseMoved(adaptEvent(e, ic, p));
}
}
iwc.getImagePlus().mouseMoved(icc.offScreenX(x), icc.offScreenY(y));
storeCanvasState(icc);
}
public void mouseDragged(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
Point p;
Point oldp;
oldX = x; oldY = y;
x = e.getX();
y = e.getY();
p = new Point(x, y);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
ic = iw.getCanvas();
if (cCoords.getState() && iw != iwc) {
p = getMatchingCoords(ic, icc, x, y);
oldp = getMatchingCoords(ic, icc, oldX, oldY);
} else {
p = new Point(x, y);
}
Roi roi = imp.getRoi();
if (! (roi != null && roi instanceof PolygonRoi && roi.getState() == Roi.CONSTRUCTING) )
drawSyncCursor(imp, p.x, p.y);
if(iw != iwc)
ic.mouseDragged(adaptEvent(e, ic, p));
}
}
storeCanvasState(icc);
}
public void mouseClicked(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
if (Toolbar.getToolId()!= Toolbar.MAGNIFIER &&
(e.isPopupTrigger() || (e.getModifiers() & MouseEvent.META_MASK)!=0)) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
Point p;
p = new Point(x,y);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
if(iw != iwc) {
ic = iw.getCanvas();
if (cCoords.getState()) {
p = getMatchingCoords(ic, icc, x, y);
}
ic.mouseClicked(adaptEvent(e, ic, p));
}
}
}
storeCanvasState(icc);
}
public void mouseEntered(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
Point p;
p = new Point(x,y);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
if(iw != iwc) {
ic = iw.getCanvas();
if (cCoords.getState()) {
p = getMatchingCoords(ic, icc, x, y);
}
ic.mouseEntered(adaptEvent(e, ic, p));
}
}
}
storeCanvasState(icc);
}
public void mouseExited(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
Point p;
p = new Point(x,y);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
ic = iw.getCanvas();
if (cCoords.getState() && iw != iwc)
p = getMatchingCoords(ic, icc, x, y);
else {
p.x = x;
p.y = y;
}
setCursor(imp, null);
if (iw != iwc)
ic.mouseExited(adaptEvent(e, ic, p));
}
}
storeCanvasState(icc);
}
public void mousePressed(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
if (Toolbar.getToolId()!= Toolbar.MAGNIFIER &&
(e.isPopupTrigger() || (e.getModifiers() & MouseEvent.META_MASK)!=0)) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
Point p;
p = new Point(x,y);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
ic = iw.getCanvas();
ic.paint(ic.getGraphics());
if(iw != iwc) {
ic = iw.getCanvas();
if (cCoords.getState()) {
p = getMatchingCoords(ic, icc, x, y);
}
ic.mousePressed(adaptEvent(e, ic, p));
}
}
}
storeCanvasState(icc);
}
public void mouseReleased(MouseEvent e) {
if (!cCursor.getState()) return;
if (vwins == null) return;
if (Toolbar.getToolId()!= Toolbar.MAGNIFIER &&
(e.isPopupTrigger() || (e.getModifiers() & MouseEvent.META_MASK)!=0)) return;
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
int xloc = e.getX();
int yloc = e.getY();
Point p = new Point(xloc, yloc);
ImageCanvas icc = (ImageCanvas) e.getSource();
ImageWindow iwc = (ImageWindow) icc.getParent();
for(int n=0; n<vwins.size();++n) {
if (ijInstance.quitting()) {
return;
}
imp = getImageFromVector(n);
if (imp != null) {
iw = imp.getWindow();
ic = iw.getCanvas();
if (cCoords.getState())
p = getMatchingCoords(ic, icc, xloc, yloc);
Roi roi = imp.getRoi();
if (! (roi != null && roi instanceof PolygonRoi && roi.getState() == Roi.CONSTRUCTING) )
drawSyncCursor(imp, p.x, p.y);
if(iw != iwc)
ic.mouseReleased(adaptEvent(e, ic, p));
}
}
storeCanvasState(icc);
}
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof Button) {
Button bpressed = (Button)source;
if(bpressed==bSyncAll) {
if (wList == null) return;
Vector v = new Vector();
Integer I;
for(int i=0; i<wList.getItemCount();++i) {
wList.select(i);
I = (Integer)vListMap.elementAt(i);
v.addElement(I);
}
addWindows(v);
} else if(bpressed==bUnsyncAll) {
removeAllWindows();
}
} else if (wList != null && source == wList) {
addSelections();
}
}
public void itemStateChanged(ItemEvent e) {
if (wList != null && e.getSource() == wList) {
if(vwins != null) {
Integer I;
for(int n = 0; n<vwins.size();++n) {
I = (Integer)vwins.elementAt(n);
removeWindow(I);
}
vwins.removeAllElements();
}
addSelections();
}
if (cCoords != null && e.getSource() == cCoords) {
if (cScaling != null && e.getStateChange() == ItemEvent.DESELECTED)
cScaling.setState(false);
}
if (cScaling != null && e.getSource() == cScaling) {
if (cCoords != null && e.getStateChange() == ItemEvent.SELECTED)
cCoords.setState(true);
}
}
public void windowClosing(WindowEvent e) {
if(e.getSource() == this) {
removeAllWindows();
ImagePlus.removeImageListener(this);
Executer.removeCommandListener(this);
close();
}
}
public void imageOpened(ImagePlus imp) {
updateWindowList();
}
public void imageClosed(ImagePlus imp) {
updateWindowList();
}
public void imageUpdated(ImagePlus imp) {
}
protected Panel controlPanel() {
Panel p = new Panel();
BorderLayout layout = new BorderLayout();
layout.setVgap(3);
p.setLayout(layout);
p.add(buildWindowList(), BorderLayout.NORTH,0);
p.add(buildControlPanel(), BorderLayout.CENTER,1);
return p;
}
protected Component buildWindowList() {
ImagePlus img;
ImageWindow iw;
int[] imageIDs = WindowManager.getIDList();
if(imageIDs != null) {
int size;
if (imageIDs.length < 10) {
size = imageIDs.length;
} else {
size = 10;
}
wList = new java.awt.List(size, true);
vListMap = new Vector();
for(int n=0; n<imageIDs.length;++n) {
ImagePlus imp = WindowManager.getImage(imageIDs[n]);
if (imp == null) continue; vListMap.addElement(Integer.valueOf(imageIDs[n]));
wList.add(imp.getTitle());
if ( vwins != null && vwins.contains(Integer.valueOf(imageIDs[n])) ) {
wList.select(n);
}
}
if (vwins != null && vwins.size() != 0) {
for (int n=0; n<vwins.size(); ++n) {
if(! vListMap.contains(vwins.elementAt(n))) {
vwins.removeElementAt(n);
n -= 1;
}
}
}
wList.addItemListener(this);
wList.addActionListener(this);
return (Component)wList;
}
else {
Label label = new Label("No windows to select.");
wList = null;
vListMap = null;
vwins = null;
return (Component)label;
}
}
protected Panel buildControlPanel() {
GridLayout layout = new GridLayout(4,2);
layout.setVgap(2);
layout.setHgap(2);
Panel p = new Panel(layout);
cCursor = new Checkbox("Sync cursor", true);
cCursor.addKeyListener(ijInstance);
p.add(cCursor);
cSlice = new Checkbox("Sync z-slices",true);
cSlice.addKeyListener(ijInstance);
p.add(cSlice);
cChannel = new Checkbox("Sync channels", true);
cChannel.addKeyListener(ijInstance);
p.add(cChannel);
cFrame = new Checkbox("Sync t-frames", true);
cFrame.addKeyListener(ijInstance);
p.add(cFrame);
cCoords = new Checkbox("Image coordinates", true);
cCoords.addItemListener(this);
cCoords.addKeyListener(ijInstance);
p.add(cCoords);
cScaling = new Checkbox("Image scaling", false);
cScaling.addItemListener(this);
cScaling.addKeyListener(ijInstance);
p.add(cScaling);
bSyncAll = new Button("Synchronize All");
bSyncAll.addActionListener(this);
bSyncAll.addKeyListener(ijInstance);
p.add(bSyncAll);
bUnsyncAll = new Button("Unsynchronize All");
bUnsyncAll.addActionListener(this);
bUnsyncAll.addKeyListener(ijInstance);
p.add(bUnsyncAll);
return p;
}
protected Rectangle boundingRect(int x, int y,
int oldX, int oldY) {
int dx = Math.abs(oldX - x)/2;
int dy = Math.abs(oldY - y)/2;
int xOffset = dx + SCALE * SZ;
int yOffset = dy + SCALE * SZ;
int xCenter = (x + oldX)/2;
int yCenter = (y + oldY)/2;
int xOrg = Math.max(xCenter - xOffset,0);
int yOrg = Math.max(yCenter - yOffset,0);
int w = 2 * xOffset;
int h = 2 * yOffset;
return new Rectangle(xOrg, yOrg, w, h);
}
protected void updateWindowList() {
Component newWindowList = buildWindowList();
GUI.scale(newWindowList);
panel.remove(0);
panel.add(newWindowList,BorderLayout.NORTH,0);
pack();
}
private void addSelections() {
if(wList == null) return;
int[] listIndexes = wList.getSelectedIndexes();
Integer I;
Vector v = new Vector();
for(int n=0; n<listIndexes.length;++n) {
I = (Integer)vListMap.elementAt(listIndexes[n]);
v.addElement(I);
}
addWindows(v);
}
private void addWindows(Vector v) {
Integer I;
ImagePlus imp;
ImageWindow iw;
if(vwins == null && v.size() > 0)
vwins = new Vector();
for(int n=0; n<v.size();++n) {
I = (Integer)v.elementAt(n);
if(!vwins.contains(I)) {
imp = WindowManager.getImage(I.intValue());
if (imp != null) {
iw = imp.getWindow();
iw.getCanvas().addMouseMotionListener(this);
iw.getCanvas().addMouseListener(this);
vwins.addElement(I);
}
}
}
}
private void removeAllWindows() {
if(vwins != null) {
Integer I;
for(int n = 0; n<vwins.size();++n) {
I = (Integer)vwins.elementAt(n);
removeWindow(I);
}
vwins.removeAllElements();
}
if (wList == null) return;
for(int n=0;n<wList.getItemCount();++n)
wList.deselect(n);
}
private void removeWindow(Integer I) {
ImagePlus imp;
ImageWindow iw;
ImageCanvas ic;
imp = WindowManager.getImage(I.intValue());
if (imp != null) {
iw = imp.getWindow();
if (iw != null) {
ic = iw.getCanvas();
if (ic != null) {
ic.removeMouseListener(this);
ic.removeMouseMotionListener(this);
ic.paint(ic.getGraphics());
}
}
}
}
private void drawSyncCursor(ImagePlus imp, int x, int y) {
ImageCanvas ic = imp.getCanvas();
if (ic==null) return;
double xpSZ = ic.offScreenXD(x+SZ);
double xmSZ = ic.offScreenXD(x-SZ);
double ypSZ = ic.offScreenYD(y+SZ);
double ymSZ = ic.offScreenYD(y-SZ);
double xp2 = ic.offScreenXD(x+2);
double xm2 = ic.offScreenXD(x-2);
double yp2 = ic.offScreenYD(y+2);
double ym2 = ic.offScreenYD(y-2);
GeneralPath path = new GeneralPath();
path.moveTo(xmSZ, ymSZ); path.lineTo(xm2, ym2);
path.moveTo(xpSZ, ypSZ); path.lineTo(xp2, yp2);
path.moveTo(xpSZ, ymSZ); path.lineTo(xp2, ym2);
path.moveTo(xmSZ, ypSZ); path.lineTo(xm2, yp2);
setCursor(imp, new ShapeRoi(path));
}
public synchronized void setCursor(ImagePlus imp, Roi cursor) {
Overlay overlay2 = imp.getOverlay();
if (overlay2!=null) {
for (int i = overlay2.size()-1; i>=0; i--) {
Roi roi2 = overlay2.get(i);
if (roi2.isCursor())
overlay2.remove(i);
}
if (cursor==null) {
imp.setOverlay(overlay2);
return;
}
} else
overlay2 = new Overlay();
if (cursor!=null) {
overlay2.add(cursor);
cursor.setStrokeColor(Color.red);
cursor.setStrokeWidth(2);
cursor.setNonScalable(true);
cursor.setIsCursor(true);
imp.setOverlay(overlay2);
}
}
private void storeCanvasState(ImageCanvas ic) {
currentMag = ic.getMagnification();
currentSrcRect = new Rectangle(ic.getSrcRect());
}
public ImagePlus getImageFromVector(int n) {
if (vwins == null || n<0 || vwins.size() < n+1) return null;
ImagePlus imp;
imp = WindowManager.getImage(((Integer)vwins.elementAt(n)).intValue());
if (imp.isLocked()) return null; return imp;
}
public String getImageTitleFromVector(int n) {
if (vwins == null || n<0 || vwins.size() < n+1) return "";
ImagePlus imp;
imp = WindowManager.getImage(((Integer)vwins.elementAt(n)).intValue());
String title = imp.getTitle();
if (title.length()>=4 && (title.substring(title.length()-4)).equalsIgnoreCase(".tif")) {
title = title.substring(0, title.length()-4);
} else if (title.length()>=5 && (title.substring(title.length()-5)).equalsIgnoreCase(".tiff")) {
title = title.substring(0, title.length()-5);
}
return title;
}
public int getIndexOfImage(ImagePlus image) {
int index = -1;
ImagePlus imp;
if (vwins == null || vwins.size() == 0)
return index;
for (int n=0; n<vwins.size(); n++){
imp = WindowManager.getImage(((Integer)vwins.elementAt(n)).intValue());
if (imp == image) {
index = n;
break;
}
}
return index;
}
protected Point getMatchingCoords(ImageCanvas ic, ImageCanvas icc, int x, int y) {
double xOffScreen = currentSrcRect.x + (x/currentMag);
double yOffScreen = currentSrcRect.y + (y/currentMag);
if (cScaling.getState()) {
Calibration cal = ((ImageWindow)ic.getParent()).getImagePlus().getCalibration();
Calibration curCal = ((ImageWindow)icc.getParent()).getImagePlus().getCalibration();
xOffScreen = ((xOffScreen-curCal.xOrigin)*curCal.pixelWidth)/cal.pixelWidth+cal.xOrigin;
yOffScreen = ((yOffScreen-curCal.yOrigin)*curCal.pixelHeight)/cal.pixelHeight+cal.yOrigin;
}
int xnew = ic.screenXD(xOffScreen);
int ynew = ic.screenYD(yOffScreen);
return new Point(xnew, ynew);
}
private MouseEvent adaptEvent(MouseEvent e, Component c, Point p) {
return new MouseEvent(c, e.getID(), e.getWhen(), e.getModifiers(),
p.x, p.y, e.getClickCount(), e.isPopupTrigger());
}
public Insets getInsets() {
Insets i = super.getInsets();
return new Insets(i.top+10, i.left+10, i.bottom+10, i.right+10);
}
public void close() {
super.close();
instance = null;
location = getLocation();
}
public static SyncWindows getInstance() {
return instance;
}
public String commandExecuting(String command) {
if (vwins!=null && cScaling!=null && cScaling.getState() && ("In [+]".equals(command) || "Out [-]".equals(command))) {
ImagePlus imp = WindowManager.getCurrentImage();
ImageCanvas cic = imp!=null?imp.getCanvas():null;
if (cic==null)
return command;
Point loc = cic.getCursorLoc();
if (!cic.cursorOverImage()) {
Rectangle srcRect = cic.getSrcRect();
loc.x = srcRect.x + srcRect.width/2;
loc.y = srcRect.y + srcRect.height/2;
}
int sx = cic.screenX(loc.x);
int sy = cic.screenY(loc.y);
for (int i=0; i<vwins.size(); i++) {
imp = getImageFromVector(i);
if (imp!=null) {
ImageCanvas ic = imp.getCanvas();
if (ic!=cic) {
if ("In [+]".equals(command))
ic.zoomIn(sx, sy);
else
ic.zoomOut(sx, sy);
if (ic.getMagnification()<=1.0)
imp.repaintWindow();
}
}
}
}
return command;
}
}
interface DisplayChangeListener extends java.util.EventListener {
public void displayChanged(DisplayChangeEvent e);
}
class DisplayChangeEvent extends EventObject {
public static final int X = 1;
public static final int Y = 2;
public static final int Z = 3;
public static final int ZOOM = 4;
public static final int T = 5;
public static final int CHANNEL = 6;
private int type;
private int value;
public DisplayChangeEvent(Object source, int type, int value) {
super(source);
this.type = type;
this.value = value;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
class IJEventMulticaster extends AWTEventMulticaster implements DisplayChangeListener {
IJEventMulticaster(EventListener a, EventListener b) {
super(a,b);
}
public void displayChanged(DisplayChangeEvent e) {
((DisplayChangeListener)a).displayChanged(e);
((DisplayChangeListener)b).displayChanged(e);
}
public static DisplayChangeListener add(DisplayChangeListener a, DisplayChangeListener b) {
return (DisplayChangeListener)addInternal(a, b);
}
public static DisplayChangeListener remove(DisplayChangeListener l, DisplayChangeListener oldl) {
return (DisplayChangeListener)removeInternal(l, oldl);
}
}