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;
}
}
}