package net.sourceforge.fidocadj; import java.util.Vector; import java.util.Locale; import java.util.MissingResourceException; import java.io.*; import android.view.MotionEvent; import android.view.View; import android.graphics.*; import android.content.*; import android.view.*; import android.graphics.Paint.*; import android.util.AttributeSet; import android.os.Handler; import android.app.Activity; import net.sourceforge.fidocadj.circuit.model.*; import net.sourceforge.fidocadj.primitives.*; import net.sourceforge.fidocadj.geom.*; import net.sourceforge.fidocadj.circuit.views.*; import net.sourceforge.fidocadj.circuit.controllers.*; import net.sourceforge.fidocadj.graphic.android.*; import net.sourceforge.fidocadj.layers.*; import net.sourceforge.fidocadj.dialogs.*; /** Android Editor view: draw the circuit inside this view. This is one of the most important classes, as it is responsible of all editing actions. <pre> This file is part of FidoCadJ. FidoCadJ is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FidoCadJ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FidoCadJ. If not, see <http://www.gnu.org/licenses/>. Copyright 2014-2015 by Davide Bucci, Dante Loi </pre> The circuit panel will contain the whole drawing. @author Davide Bucci, Dante Loi */ public class FidoEditor extends View implements PrimitivesParInterface { GestureDetector gestureDetector; int desiredWidth = 1500; int desiredHeight = 800; private DrawingModel dm; private Drawing dd; private MapCoordinates cs; private ParserActions pa; private UndoActions ua; public EditorActions ea; public ContinuosMoveActions eea; private HandleActions haa; private CopyPasteActions cpa; private SelectionActions sa; // ********** RULER ********** private boolean ruler; // Is it to be drawn? private int rulerStartX; private int rulerStartY; private int rulerEndX; private int rulerEndY; // ***** ZOOM AND PANNING ***** private int mx; private int my; private int oldDist; private int newDist; private double origZoom; private int oldScrollX; private int oldScrollY; private int oldCenterX; private int oldCenterY; // ********** EDITING ********* private RectF evidenceRect; private Context cc; final Handler handler = new Handler(); private boolean showGrid; /** Public constructor. @param context the current context. @param attrs the attribute set. */ public FidoEditor(Context context, AttributeSet attrs) { super(context, attrs); cc=context; init(); evidenceRect = null; gestureDetector = new GestureDetector(context, new GestureListener()); } /** Adopt the standard layer description and color. The drawing model should be already set when calling initLayers. */ public void initLayers() { Vector<LayerDesc> layerDesc = StandardLayers.createStandardLayers(cc); dm.setLayers(layerDesc); } /** Initialize the view and prepare everything for the drawing. */ private void init() { this.setMeasuredDimension(this.desiredWidth, this.desiredHeight); dm = new DrawingModel(); initLayers(); dd = new Drawing(dm); pa = new ParserActions(dm); ua = new UndoActions(pa); sa = new SelectionActions(dm); ea = new EditorActions(dm, sa, ua); eea = new ContinuosMoveActions(dm, sa, ua, ea); haa = new HandleActions(dm, ea, sa, ua); cpa=new CopyPasteActions(dm, ea, sa, pa, ua, (FidoMain)cc); // Specify a reasonable tolerance so you can select objects and handles // with your finger. ea.setSelectionTolerance(20* getResources().getDisplayMetrics().densityDpi/112); cpa.setShiftCopyPaste(true); eea.setActionSelected(ElementsEdtActions.SELECTION); eea.setPrimitivesParListener(this); showGrid = true; readInternalLibraries(); //StringBuffer s=new StringBuffer(createTestPattern()); //pa.parseString(s); ua.saveUndoState(); cs = new MapCoordinates(); cs.setXMagnitude(3*getResources().getDisplayMetrics().densityDpi/112); cs.setYMagnitude(3*getResources().getDisplayMetrics().densityDpi/112); cs.setXGridStep(5); cs.setYGridStep(5); // Courier New is the standard on PC, but it is not available on // Android. Here the system will find a substitute. dm.setTextFont("Courier New", 3, null); //turn off hw accelerator setLayerType(View.LAYER_TYPE_SOFTWARE, null); } /** Read the internal libraries (specified as raw resources). */ private void readInternalLibraries() { BufferedReader stdLib; BufferedReader ihram; BufferedReader elettrotecnica; BufferedReader pcb; BufferedReader eylib; // Libraries are available in Italian or in English String lang=""; try { lang=Locale.getDefault().getISO3Language(); } catch (MissingResourceException E) { // Not a big issue. Show the English version of the libs. lang=""; } if("ita".equals(lang)) { // Italian version (only for italian locale) stdLib = new BufferedReader(new InputStreamReader(cc.getResources() .openRawResource(R.raw.fcdstdlib))); ihram = new BufferedReader(new InputStreamReader(cc.getResources() .openRawResource(R.raw.ihram))); elettrotecnica=new BufferedReader(new InputStreamReader( cc.getResources().openRawResource(R.raw.elettrotecnica))); pcb = new BufferedReader(new InputStreamReader(cc.getResources() .openRawResource(R.raw.pcb))); } else { // English version stdLib = new BufferedReader( new InputStreamReader(cc.getResources().openRawResource( R.raw.fcdstdlib_en))); ihram = new BufferedReader( new InputStreamReader(cc.getResources().openRawResource( R.raw.ihram_en))); elettrotecnica = new BufferedReader( new InputStreamReader(cc.getResources().openRawResource( R.raw.elettrotecnica_en))); pcb = new BufferedReader( new InputStreamReader(cc.getResources().openRawResource( R.raw.pcb_en))); } eylib = new BufferedReader( new InputStreamReader(cc.getResources().openRawResource( R.raw.ey_libraries))); try { pa.readLibraryBufferedReader(stdLib, ""); pa.readLibraryBufferedReader(pcb, "pcb"); pa.readLibraryBufferedReader(ihram, "ihram"); pa.readLibraryBufferedReader(elettrotecnica, "elettrotecnica"); pa.readLibraryBufferedReader(eylib, "ey_libraries"); } catch (IOException E) { } } /** Convert the current drawing to a string description (FidoCadJ code). @return the code describing the circuit. */ public String getText() { return pa.getText(true).toString(); } /** Get the EditorActions controller for the drawing. @return the EditorActions object. */ public EditorActions getEditorActions() { return ea; } /** Get the ContinuosMoveActions for the drawing. @return the ContinuosMoveActions object. */ public ContinuosMoveActions getContinuosMoveActions() { return eea; } /** Get the CopyPasteActions controller for the drawing. @return the CopyPasteActions object. */ public CopyPasteActions getCopyPasteActions() { return cpa; } /** Get the UndoActions controller for the drawing. @return the UndoActions object. */ public UndoActions getUndoActions() { return ua; } /** Get the ParserActions controller for the drawing. @return the ParserActions object. */ public ParserActions getParserActions() { return pa; } /** Get the DrawingModel object containing the drawing. @return the drawing model. */ public DrawingModel getDrawingModel() { return dm; } /** Set the DrawingModel object containing the drawing. @param d the model to be employed. */ public void setDrawingModel(DrawingModel d) { dm=d; } /** Get the current selection controller @return the selection controller */ public SelectionActions getSelectionActions() { return sa; } /** Draw the drawing on the given canvas. @param canvas the canvas where the drawing will be drawn. */ @Override protected void onDraw(Canvas canvas) { canvas.drawARGB(255, 255, 255, 255); GraphicsAndroid g = new GraphicsAndroid(canvas); if(showGrid){ g.drawGrid(cs, (int)getScrollX(), (int)getScrollY(), (int)(getScrollX()+getWidth()), (int)(getScrollY()+getHeight())); } // Show a sort of an arrow, to indicate that there is the library // hidden on the right. float xs = getScrollX()+getWidth(); float ys = getScrollY()+getHeight()/2.0f; float mult=g.getScreenDensity()/112.0f; Paint p= new Paint(Color.GRAY); p.setStrokeWidth(3.0f*mult); p.setAntiAlias(true); p.setStrokeCap(Cap.ROUND); p.setStrokeJoin(Join.ROUND); canvas.drawLine(xs-12.0f*mult, ys, xs-2.0f*mult, ys-20.0f*mult,p); canvas.drawLine(xs-12.0f*mult, ys, xs-2.0f*mult, ys+20.0f*mult,p); // Draw the objects in the database dd.draw(g, cs); // Draw the handles of all selected primitives. dd.drawSelectedHandles(g, cs); if (evidenceRect != null && eea.actionSelected == ElementsEdtActions.SELECTION) { Paint rectPaint = new Paint(); rectPaint.setColor(Color.GREEN); rectPaint.setStyle(Style.STROKE); rectPaint.setStrokeWidth(mult*1.3f); canvas.drawRect(evidenceRect, rectPaint); } else { evidenceRect = null; } eea.showClicks(g, cs); } /** Handles scroll events. @param x current x position. @param y current y position. @param oldx old x position. @param oldy old y position. */ @Override protected void onScrollChanged(int x, int y, int oldx, int oldy) { super.onScrollChanged(x, y, oldx, oldy); if(x < 0) scrollBy(-x,0); else if(y < 0) scrollBy(0,-y); else scrollBy(0,0); } /** Reacts to a touch event. In reality, since we need to handle a variety of different events, there is somehow a mix between the low level onTouchEvent and the GestureListener (which does not handle slide and move events). @param event the touch or motion event. @return true if a touch has been processed. */ @Override public boolean onTouchEvent(MotionEvent event) { gestureDetector.onTouchEvent(event); if(event.getPointerCount()<=0) return false; int action = event.getAction() & MotionEvent.ACTION_MASK; int curX, curY; /* Handle all actions related to selection */ if(eea.getSelectionState() == ElementsEdtActions.SELECTION) { mx = (int) event.getX()+getScrollX(); my = (int) event.getY()+getScrollY(); // Handle selection events. switch (action) { case MotionEvent.ACTION_DOWN: haa.dragHandleStart(mx, my, ea.getSelectionTolerance(), false, cs); break; case MotionEvent.ACTION_MOVE: haa.dragHandleDrag(this, mx, my, cs, false); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: haa.dragHandleEnd(this, mx, my, false, cs); break; default: break; } invalidate(); } /* Handle all actions related to move/zoom */ if (eea.getSelectionState() == ElementsEdtActions.HAND) { //Handle Scrolling and zooming events switch (action) { case MotionEvent.ACTION_DOWN: mx = (int)event.getX(); my = (int)event.getY(); if(event.getPointerCount() == 1) { oldDist=-1; // Distinguish 1-finger events (pan) // from 2-fingers ones (pinch, zoom). } break; case MotionEvent.ACTION_POINTER_DOWN: if(event.getPointerCount() == 2) { // Begin of a zoom gesture oldDist = spacing(event); oldCenterX=(int)(event.getX(0) + event.getX(1))/2; oldCenterY=(int)(event.getY(0) + event.getY(1))/2; oldScrollX=getScrollX(); oldScrollY=getScrollY(); } else { oldDist=-1; // This is just to be on the safe side } origZoom=cs.getXMagnitude(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_MOVE: // Handle the zoom gesture. if(event.getPointerCount() == 2 && oldDist>0) { newDist = spacing(event); double scale = (double)newDist / (double)oldDist; // The new zoom is calculated from the original one // saved in MotionEvent.ACTION_POINTER_DOWN. double newZoom=scale*origZoom; cs.setXMagnitude(newZoom); cs.setYMagnitude(newZoom); // There might be some roundup or limiting while // setting the new zoom. This ensures that a coherent // value for newZoom is used for what follows. newZoom=cs.getXMagnitude(); int corrX=(int)((oldCenterX) /origZoom*newZoom)-oldCenterX; int corrY=(int)((oldCenterY) /origZoom*newZoom)-oldCenterY; setScrollX((int)(oldScrollX/origZoom*newZoom)+corrX); setScrollY((int)(oldScrollY/origZoom*newZoom)+corrY); invalidate(); } else if(oldDist<0) { // Panning curX = (int)event.getX(); curY = (int)event.getY(); scrollBy((mx - curX), (my - curY)); mx = curX; my = curY; } break; default: break; } } return true; } /** @return the distance between two points specified by a multiple event. @param event the multi touch event. */ private int spacing(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); int dist = (int) Math.sqrt(x * x + y * y); return dist; } /** Inform Android's operating system of the size of the view. @param widthMeasureSpec @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; //Measure Width if (widthMode == MeasureSpec.EXACTLY) { //Must be this size width = widthSize; } else if (widthMode == MeasureSpec.AT_MOST) { //Can't be bigger than... width = Math.min(desiredWidth, widthSize); } else { //Be whatever you want width = desiredWidth; } //Measure Height if (heightMode == MeasureSpec.EXACTLY) { //Must be this size height = heightSize; } else if (heightMode == MeasureSpec.AT_MOST) { //Can't be bigger than... height = Math.min(desiredHeight, heightSize); } else { //Be whatever you want height = desiredHeight; } //MUST CALL THIS setMeasuredDimension(width, height); } /** Show a dialog which allows the user modify the parameters of a given primitive. If more than one primitive is selected, modify only the layer of all selected primitives. */ public void setPropertiesForPrimitive() { GraphicPrimitive gp=sa.getFirstSelectedPrimitive(); if (gp==null) return; Vector<ParameterDescription> v; if (sa.isUniquePrimitiveSelected()) { v=gp.getControls(); } else { // If more than a primitive is selected, v=new Vector<ParameterDescription>(1); ParameterDescription pd = new ParameterDescription(); pd.parameter=new LayerInfo(gp.getLayer()); pd.description="TODO: add a reference to a resource"; v.add(pd); } DialogParameters dp = DialogParameters.newInstance(v, false, dm.getLayers()); dp.show( ((Activity)cc).getFragmentManager(), ""); } /** This function is a callback which is used by DialogParameters to save the useful data. @param v the vector containing the layers. */ public void saveCharacteristics(Vector<ParameterDescription> v) { //android.util.Log.e("FidoCadJ", "saveCharacteristics: "+v); GraphicPrimitive gp=sa.getFirstSelectedPrimitive(); android.util.Log.e("FidoCadJ", "saveCharacteristics this= "+this); if (sa.isUniquePrimitiveSelected()) { gp.setControls(v); } else { ParameterDescription pd=(ParameterDescription)v.get(0); if (pd.parameter instanceof LayerInfo) { int l=((LayerInfo)pd.parameter).getLayer(); ea.setLayerForSelectedPrimitives(l); } else { android.util.Log.e("FidoCadJ", "Warning: unexpected parameter! (layer)"); } } dm.setChanged(true); // We need to check and sort the layers, since the user can // change the layer associated to a given primitive thanks to // the dialog window which has been shown. dm.sortPrimitiveLayers(); ua.saveUndoState(); invalidate(); } /** Selects the closest object to the given point (in logical coordinates) and pops up a dialog for the editing of its Param_opt. @param x the x logical coordinate of the point used for the selection @param y the y logical coordinate of the point used for the selection */ public void selectAndSetProperties(int x, int y) { sa.setSelectionAll(false); ea.handleSelection(cs, x, y, false); invalidate(); setPropertiesForPrimitive(); } /** Implementation of the PrimitivesParInterface interface. Show the popup menu. In Android, the menu can be centered inside the current view. @param x the x position of the menu (can be discarded on Android). @param y the y position of the menu (can be discarded on Android). */ public void showPopUpMenu(int x, int y) { ((Activity) cc).registerForContextMenu(this); ((Activity) cc).openContextMenu(this); ((Activity) cc).unregisterForContextMenu(this); } /** Increases or decreases the zoom by a step of 33% @param increase if true, increase the zoom, if false decrease @param x coordinate to which center the viewport (screen coordinates) @param y coordinate to which center the viewport (screen coordinates) */ public void changeZoomByStep(boolean increase, int x, int y) { // Not needed with Android. } /** Calculate an optimum zoom which fits to the document. Move the scroll bars accordingly. */ public void zoomToFit() { zoomOrPanToFit(true); } /** Pan the drawing so it is visible. Does not change the zoom */ public void panToFit() { zoomOrPanToFit(false); } private void zoomOrPanToFit(boolean scaleZoom) { // At first get the size in which the drawing should be fit int sizex=getWidth(); int sizey=getHeight(); // Calculate the zoom to fit scale MapCoordinates mp; mp = DrawingSize.calculateZoomToFit(dm, sizex, sizey, true); double z=mp.getXMagnitude(); // Set the new coordinate system and force a redraw. if(scaleZoom) getMapCoordinates().setMagnitudes(z, z); setScrollX((int)mp.getXCenter()); setScrollY((int)mp.getYCenter()); invalidate(); } /** Makes sure the object gets focus. */ public void getFocus() { // Not needed with Android. } /** Forces a repaint event. */ public void forcesRepaint() { invalidate(); } /** Forces a repaint, specify the region to be updated. @param a not used. @param b not used. @param c not used. @param d not used. */ public void forcesRepaint(int a, int b, int c, int d) { invalidate(); } /** Activate and sets an evidence rectangle which will be put on screen at the next redraw. All sizes are given in pixel. @param x the x coordinate of the left top corner @param y the y coordinate of the left top corner @param w the width of the rectangle @param h the height of the rectangle */ public void setEvidenceRect(int x, int y, int w, int h) { evidenceRect = new RectF(x, y, x+w, y+h); } /** Get the current coordinate mapping object. @return the current coordinate mapping object. */ public MapCoordinates getMapCoordinates() { return cs; } /** Set the current coordinate mapping object. @param c the current coordinate mapping object. */ public void setMapCoordinates(MapCoordinates c) { cs=c; } /** Sets whether the grid showing the editing points should be shown or not. @param s true if the grid is to be drawn. */ public void setShowGrid(boolean s) { showGrid=s; } /** Gets if the editing grid has to be shown. @return true if the grid is drawn. */ public boolean getShowGrid() { return showGrid; } /** Gesture listener: useful to detect for long taps to show contextual menu. */ private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent event) { if(eea.getSelectionState()!=ElementsEdtActions.SELECTION) return false; int x, y; if(event.getPointerCount()>0) { x = (int)event.getX(0)+getScrollX(); y = (int)event.getY(0)+getScrollY(); } else { return false; } ruler=false; rulerStartX = x; rulerStartY = y; rulerEndX = x; rulerEndY = y; long start = System.currentTimeMillis(); haa.dragHandleStart(x, y, ea.getSelectionTolerance(), false, cs); long millis=System.currentTimeMillis()-start; android.util.Log.e("fidocadj", "dragHandleStart done in "+millis+ " ms"); invalidate(); return true; } @Override public boolean onSingleTapUp(MotionEvent event) { return tapUpHandler(event, false, false); } @Override public void onLongPress(MotionEvent event) { tapUpHandler(event, true, false); } @Override public boolean onDoubleTap(MotionEvent event) { return tapUpHandler(event, false, true); } private boolean tapUpHandler(MotionEvent event, boolean longTap, boolean doubleTap) { int x, y; if(event.getPointerCount()>0) { x = (int)event.getX(0)+getScrollX(); y = (int)event.getY(0)+getScrollY(); } else { return false; } // If we are in the selection state, either we are ending the // editing // of an element (and thus the dragging of a handle) or we are // making a click. if(eea.actionSelected==ElementsEdtActions.SELECTION) { android.util.Log.e("f", "x="+x+" rulerStartX="+rulerStartX); if(Math.abs(x-rulerStartX)>10 || Math.abs(y-rulerStartY)>10) { haa.dragHandleEnd(FidoEditor.this,x, y, false, cs); } else { android.util.Log.d("f", "long: "+longTap+" double: " +doubleTap); eea.handleClick(cs, x, y, longTap, false,doubleTap); } } else { android.util.Log.d("f", "long: "+longTap+" double: " +doubleTap); eea.handleClick(cs, x, y, longTap, false,doubleTap); } invalidate(); return true; } } }