package org.csstudio.swt.widgets.figureparts; import org.csstudio.swt.widgets.figures.IntensityGraphFigure; import org.csstudio.swt.widgets.figures.IntensityGraphFigure.GraphArea; import org.csstudio.swt.widgets.figures.IntensityGraphFigure.ICroppedDataSizeListener; import org.csstudio.swt.widgets.figures.IntensityGraphFigure.IROIInfoProvider; import org.csstudio.swt.widgets.figures.IntensityGraphFigure.IROIListener; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.Cursors; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.FigureListener; import org.eclipse.draw2d.FocusEvent; import org.eclipse.draw2d.FocusListener; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.MouseEvent; import org.eclipse.draw2d.MouseListener; import org.eclipse.draw2d.MouseMotionListener; import org.eclipse.draw2d.RectangleFigure; import org.eclipse.draw2d.TextUtilities; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PrecisionPoint; import org.eclipse.draw2d.geometry.PrecisionRectangle; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; public class ROIFigure extends Figure { abstract class CommonDragger extends MouseMotionListener.Stub implements MouseListener { protected Point start; protected Rectangle startROIBounds; protected boolean armed; @Override public void mousePressed(MouseEvent me) { start = me.getLocation(); startROIBounds = roiGeoBounds.getCopy(); me.consume(); } @Override public void mouseDoubleClicked(MouseEvent me) { } @Override public void mouseReleased(MouseEvent me) { if(armed){ armed = false; updateROIBounds(me); fireROIUpdated(); me.consume(); } } protected abstract void updateROIBounds(MouseEvent me); @Override public void mouseDragged(MouseEvent me) { armed = true; updateROIBounds(me); me.consume(); } } class ROIRectDragger extends CommonDragger { @Override public void mousePressed(MouseEvent me) { requestFocus(); super.mousePressed(me); } /** * @param me */ @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; int dy = me.y - start.y; RECT_SINGLETON.setBounds(startROIBounds.x + dx, startROIBounds.y + dy, startROIBounds.width, startROIBounds.height); setROIGeoBounds(RECT_SINGLETON.x, RECT_SINGLETON.y, RECT_SINGLETON.width, RECT_SINGLETON.height); } } class HandlerBoundsCalulator { public Rectangle calcBoundsFromROIBounds(Rectangle roiBounds){ //the bounds for handler 0 on left top corner return new Rectangle(roiBounds.x - HANDLE_SIZE/2, roiBounds.y-HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE); } } class ResizeHandler extends RectangleFigure{ private HandlerBoundsCalulator handlerBoundsCalulator; /**Constructor. * @param index index of the handler. Count from 0 on clockwise. */ public ResizeHandler(int index) { super(); setFill(true); setBackgroundColor(ColorConstants.black); setOutline(true); setForegroundColor(ColorConstants.white); Cursor cursor = null; CommonDragger dragger = null; switch (index) { case 0: cursor = Cursors.SIZENW; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; int dy = me.y - start.y; setROIGeoBounds(me.x, me.y, startROIBounds.width - dx, startROIBounds.height - dy); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(); break; case 1: cursor = Cursors.SIZEN; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dy = me.y - start.y; setROIGeoBounds(startROIBounds.x, me.y, startROIBounds.width, startROIBounds.height - dy); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate(roiBounds.width/2, 0); } }; break; case 2: cursor = Cursors.SIZENE; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; int dy = me.y - start.y; setROIGeoBounds(startROIBounds.x, startROIBounds.y + dy, startROIBounds.width + dx, startROIBounds.height - dy); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate(roiBounds.width, 0); } }; break; case 3: cursor = Cursors.SIZEE; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; setROIGeoBounds(startROIBounds.x, startROIBounds.y, startROIBounds.width + dx, startROIBounds.height); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate( roiBounds.width, roiBounds.height/2); } }; break; case 4: cursor = Cursors.SIZESE; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; int dy = me.y - start.y; setROIGeoBounds(startROIBounds.x, startROIBounds.y, startROIBounds.width + dx, startROIBounds.height + dy); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate(roiBounds.width, roiBounds.height); } }; break; case 5: cursor = Cursors.SIZES; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dy = me.y - start.y; setROIGeoBounds(startROIBounds.x, startROIBounds.y, startROIBounds.width, startROIBounds.height+dy); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate( roiBounds.width/2, roiBounds.height); } }; break; case 6: cursor = Cursors.SIZESW; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; int dy = me.y - start.y; setROIGeoBounds(startROIBounds.x +dx, startROIBounds.y, startROIBounds.width - dx, startROIBounds.height + dy); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate(0, roiBounds.height); } }; break; case 7: cursor = Cursors.SIZEW; dragger = new CommonDragger() { @Override protected void updateROIBounds(MouseEvent me) { int dx = me.x - start.x; setROIGeoBounds(startROIBounds.x + dx, startROIBounds.y, startROIBounds.width-dx, startROIBounds.height); } }; handlerBoundsCalulator = new HandlerBoundsCalulator(){ @Override public Rectangle calcBoundsFromROIBounds( Rectangle roiBounds) { return super.calcBoundsFromROIBounds(roiBounds).translate(0, roiBounds.height/2); } }; break; default: break; } setCursor(cursor); addMouseListener(dragger); addMouseMotionListener(dragger); } public HandlerBoundsCalulator getHandlerBoundsCalulator() { return handlerBoundsCalulator; } } private static final int HANDLERS_COUNT = 8; protected static final int HANDLE_SIZE = 5; private static final Rectangle RECT_SINGLETON = new Rectangle(); /** * Geometry bounds of ROI. */ private PrecisionRectangle roiGeoBounds; /** * Data index bounds of ROI based on whole data array. */ private Rectangle roiDataBounds; /** * The handlers on left-top and right-bottom corners. */ private ResizeHandler[] resizeHandlers; private RectangleFigure roiRectFigure; private IROIListener roiListener; private IROIInfoProvider roiInfoProvider; private String name; private IntensityGraphFigure intensityGraphFigure; /**Constructor of ROI figure. * @param name name of the ROI. It must be unique for this graph. * @param color color of the ROI. * @param roiListener listener on ROI updates. Can be null. * @param roiInfoProvider provides information for the ROI. Can be null. */ public ROIFigure(IntensityGraphFigure intensityGraphFigure, String name, Color color, IROIListener roiListener, IROIInfoProvider roiInfoProvider) { this.intensityGraphFigure = intensityGraphFigure; this.name = name; this.roiListener = roiListener; this.roiInfoProvider = roiInfoProvider; setToolTip(new Label(name)); setBackgroundColor(ColorConstants.white); setForegroundColor(ColorConstants.black); roiRectFigure = new RectangleFigure(){ @Override public boolean containsPoint(int x, int y) { if (!super.containsPoint(x, y)) return false; return !Rectangle.SINGLETON.setBounds(getBounds()) .shrink(3, 3).contains(x, y); } }; roiRectFigure.setCursor(Cursors.SIZEALL); roiRectFigure.setFill(false); roiRectFigure.setOutline(true); roiRectFigure.setForegroundColor(color); ROIRectDragger roiRectDragger = new ROIRectDragger(); roiRectFigure.addMouseListener(roiRectDragger); roiRectFigure.addMouseMotionListener(roiRectDragger); setFocusTraversable(true); setRequestFocusEnabled(true); resizeHandlers = new ResizeHandler[HANDLERS_COUNT]; add(roiRectFigure); for(int i=0; i<HANDLERS_COUNT; i++){ resizeHandlers[i] = new ResizeHandler(i); add(resizeHandlers[i]); } addFocusListener(new FocusListener(){ @Override public void focusGained(FocusEvent fe) { for(Figure handler : resizeHandlers){ handler.setVisible(true); } } @Override public void focusLost(FocusEvent fe) { for(Figure handler : resizeHandlers){ handler.setVisible(false); } } }); intensityGraphFigure.addCroppedDataSizeListener(new ICroppedDataSizeListener() { @Override public void croppedDataSizeChanged(int croppedDataWidth, int croppedDataHeight) { updateROIGeoBounds(); updateChildrenBounds(); } }); } @Override public boolean containsPoint(int x, int y) { x = x - getBounds().x; y = y - getBounds().y; boolean contain = false; for(ResizeHandler handler : resizeHandlers){ contain = contain || handler.containsPoint(x, y); } return contain || roiRectFigure.containsPoint(x, y) ; } @Override protected void layout() { Rectangle clientArea = getClientArea(); if(roiDataBounds == null){ roiGeoBounds = new PrecisionRectangle(clientArea.x + clientArea.width/2 - clientArea.width/10, clientArea.y + clientArea.height/2 - clientArea.height/10, clientArea.width/5, clientArea.height/5); roiDataBounds = getROIFromGeoBounds(new PrecisionRectangle(roiGeoBounds.preciseX() + getBounds().x, roiGeoBounds.preciseY() + getBounds().y, roiGeoBounds.preciseWidth(), roiGeoBounds.preciseHeight())); fireROIUpdated(); } updateROIGeoBounds(); updateChildrenBounds(); } @Override protected void paintFigure(Graphics graphics) { if(roiInfoProvider!=null){ String text = roiInfoProvider.getROIInfo(roiDataBounds.x, roiDataBounds.y, roiDataBounds.width, roiDataBounds.height); Dimension size = TextUtilities.INSTANCE.getTextExtents(text, getFont()); graphics.pushState(); graphics.translate(getLocation()); graphics.fillRectangle(roiGeoBounds.x, roiGeoBounds.y - size.height, size.width, size.height); graphics.drawText(text, roiGeoBounds.x, roiGeoBounds.y - size.height); graphics.popState(); } super.paintFigure(graphics); } /** * */ protected void updateChildrenBounds() { roiRectFigure.setBounds(roiGeoBounds); for(ResizeHandler handler : resizeHandlers){ handler.setBounds( handler.getHandlerBoundsCalulator().calcBoundsFromROIBounds(roiGeoBounds)); } if(roiInfoProvider!=null) repaint(); } @Override protected boolean useLocalCoordinates() { return true; } public void setROIGeoBounds(int x, int y, int w, int h){ // Rectangle clA = getClientArea(); // if(x < clA.x) // x = clA.x; // else if(x>=clA.x + clA.width){ // x = clA.x + clA.width-2; // w = 1; // } // // if(y < clA.y) // y = clA.y; // else if(y>=clA.y + clA.height){ // y = clA.y + clA.height-2; // h=1; // } // // // if(w+x>clA.x + clA.width) // w = clA.x + clA.width - x; if(w <=0) w=1; // // if(h+y>clA.y + clA.height) // h = clA.y + clA.height - y; if(h <=0) h=1; roiGeoBounds.setBounds(x, y, w, h); roiDataBounds = getROIFromGeoBounds(new PrecisionRectangle(roiGeoBounds.preciseX() + getBounds().x, roiGeoBounds.preciseY() + getBounds().y, roiGeoBounds.preciseWidth(), roiGeoBounds.preciseHeight())); if(roiDataBounds.width <1 || roiDataBounds.height <1 ){ if(roiDataBounds.width <1) roiDataBounds.width =1; if(roiDataBounds.height <1) roiDataBounds.height =1; roiGeoBounds = getGeoBoundsFromROI(roiDataBounds); } updateChildrenBounds(); } public void setROIDataBounds(int xIndex, int yIndex, int width, int height){ RECT_SINGLETON.setBounds(xIndex,yIndex,width,height); if(RECT_SINGLETON.equals(roiDataBounds)) return; if(roiDataBounds == null) roiDataBounds = new PrecisionRectangle(); roiDataBounds.setBounds(xIndex, yIndex, width, height); updateROIGeoBounds(); updateChildrenBounds(); } private void updateROIGeoBounds(){ roiGeoBounds = getGeoBoundsFromROI(roiDataBounds); } public void fireROIUpdated() { if(roiListener !=null){ roiListener.roiUpdated(roiDataBounds.x, roiDataBounds.y, roiDataBounds.width, roiDataBounds.height); } } private Rectangle getROIFromGeoBounds(PrecisionRectangle roiBounds){ PrecisionPoint lt = ((GraphArea)getParent()).getDataLocation(roiBounds.preciseX(), roiBounds.preciseY()); PrecisionPoint rb = ((GraphArea)getParent()).getDataLocation(roiBounds.preciseX() + roiBounds.preciseWidth(), roiBounds.preciseY() + roiBounds.preciseHeight()); return new Rectangle((int)Math.round(lt.preciseX()) + intensityGraphFigure.getCropLeft(), (int)Math.round(lt.preciseY()) +intensityGraphFigure.getCropTop(), (int)Math.ceil(rb.preciseX() - lt.preciseX()), (int)Math.ceil(rb.preciseY() - lt.preciseY())); } private PrecisionRectangle getGeoBoundsFromROI(Rectangle roiDataBounds){ PrecisionPoint lt = ((GraphArea)getParent()).getGeoLocation(roiDataBounds.preciseX()-intensityGraphFigure.getCropLeft(), roiDataBounds.preciseY()-intensityGraphFigure.getCropTop()); PrecisionPoint rb = ((GraphArea)getParent()).getGeoLocation( roiDataBounds.preciseX() + roiDataBounds.preciseWidth() -intensityGraphFigure.getCropLeft(), roiDataBounds.preciseY() + roiDataBounds.preciseHeight() -intensityGraphFigure.getCropTop()); return new PrecisionRectangle(lt.preciseX()-getBounds().x, lt.preciseY()-getBounds().y, rb.preciseX() - lt.preciseX(), rb.preciseY() - lt.preciseY()); } public void setROIColor(Color roiColor) { roiRectFigure.setForegroundColor(roiColor); } }