/* @file DrawingSurface.java
*
* @author marco corvi
* @date nov 2011
*
* @brief TopoDroid drawing: drawing surface (canvas)
* --------------------------------------------------------
* Copyright This sowftare is distributed under GPL-3.0 or later
* See the file COPYING.
* --------------------------------------------------------
*/
package com.topodroid.DistoX;
import android.content.Context;
import android.graphics.*; // Bitmap
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.HashMap;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.EOFException;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;
/**
*/
public class DrawingSurface extends SurfaceView
implements SurfaceHolder.Callback
{
static final int DRAWING_PLAN = 1;
static final int DRAWING_PROFILE = 2;
static final int DRAWING_SECTION = 3;
static final int DRAWING_OVERVIEW = 4;
private Boolean _run;
boolean mSurfaceCreated = false;
protected DrawThread mDrawThread;
public boolean isDrawing = true;
public DrawingPath previewPath;
private SurfaceHolder mHolder; // canvas holder
private Context mContext;
private IZoomer mZoomer = null;
private AttributeSet mAttrs;
private int mWidth; // canvas width
private int mHeight; // canvas height
private long mType;
static private DrawingCommandManager commandManager = null;
static DrawingCommandManager mCommandManager1 = null;
static DrawingCommandManager mCommandManager2 = null;
static DrawingCommandManager mCommandManager3 = null;
ArrayList< String > mSplayStations; // stations where to show splays
// -----------------------------------------------------
// MANAGER CACHE
static private HashMap<String, DrawingCommandManager> mCache = new HashMap<String, DrawingCommandManager>();
static void clearCache() { mCache.clear(); }
static void addManagerToCache( String fullname )
{
if ( commandManager != null ) mCache.put( fullname, commandManager );
}
// return true if saved manager can be used
boolean resetManager( int mode, String fullname )
{
boolean ret = false;
DrawingCommandManager manager = null;
// Log.v("DistoX", "cache size " + mCache.size() );
if ( mode == DRAWING_PLAN ) {
if ( fullname != null ) manager = mCache.get( fullname );
if ( manager == null ) {
mCommandManager1 = new DrawingCommandManager();
} else {
mCommandManager1 = manager;
mCommandManager1.setDisplayPoints( false );
ret = true;
}
commandManager = mCommandManager1;
} else if ( mode == DRAWING_PROFILE ) {
if ( fullname != null ) manager = mCache.get( fullname );
if ( manager == null ) {
mCommandManager2 = new DrawingCommandManager();
} else {
mCommandManager2 = manager;
mCommandManager2.setDisplayPoints( false );
ret = true;
}
commandManager = mCommandManager2;
} else {
if ( mCommandManager3 == null ) {
mCommandManager3 = new DrawingCommandManager();
} else {
mCommandManager3.clearDrawing();
}
commandManager = mCommandManager3;
}
return ret;
}
void setManager( int mode, int type )
{
mType = type;
// Log.v( "DistoX", " set manager type " + type );
if ( mode == DRAWING_PROFILE ) {
commandManager = mCommandManager2;
} else if ( mode == DRAWING_PLAN ) {
commandManager = mCommandManager1;
} else {
commandManager = mCommandManager3;
}
}
// -----------------------------------------------------
public int width() { return mWidth; }
public int height() { return mHeight; }
// private Timer mTimer;
// private TimerTask mTask;
boolean isSelectable() { return commandManager != null && commandManager.isSelectable(); }
void setZoomer( IZoomer zoomer ) { mZoomer = zoomer; }
public DrawingSurface(Context context, AttributeSet attrs)
{
super(context, attrs);
mWidth = 0;
mHeight = 0;
mDrawThread = null;
mContext = context;
mAttrs = attrs;
mHolder = getHolder();
mHolder.addCallback(this);
// mCommandManager1 = new DrawingCommandManager();
// mCommandManager2 = new DrawingCommandManager();
commandManager = mCommandManager3;
mSplayStations = new ArrayList<String>();
}
// -------------------------------------------------------------------
void setDisplayPoints( boolean display )
{
commandManager.setDisplayPoints( display );
if ( display ) {
} else {
commandManager.clearSelected();
}
}
int getNextAreaIndex() { return commandManager.getNextAreaIndex(); }
// void setScaleBar( float x0, float y0 )
// {
// commandManager.setScaleBar(x0,y0);
// }
List< DrawingPath > getIntersectionShot( LinePoint p1, LinePoint p2 )
{
return commandManager.getIntersectionShot(p1, p2);
}
// -----------------------------------------------------------
public void setDisplayMode( int mode ) { commandManager.setDisplayMode(mode); }
public int getDisplayMode( ) { return commandManager.getDisplayMode(); }
/** apply
* X -> (x+dx)*s = x*s + dx*s
* Y -> (y+dy)*s = y*s + dy*s
*/
public void setTransform( float dx, float dy, float s )
{
commandManager.setTransform( dx, dy, s );
}
void splitLine( DrawingLinePath line, LinePoint lp ) { commandManager.splitLine( line, lp ); }
boolean removeLinePoint( DrawingPointLinePath line, LinePoint point, SelectionPoint sp )
{ return commandManager.removeLinePoint(line, point, sp); }
void deletePath( DrawingPath path )
{
isDrawing = true;
EraseCommand cmd = new EraseCommand();
commandManager.deletePath( path, cmd );
commandManager.addEraseCommand( cmd );
}
void sharpenLine( DrawingLinePath line ) { commandManager.sharpenLine( line ); }
void reduceLine( DrawingLinePath line ) { commandManager.reduceLine( line ); }
void closeLine( DrawingLinePath line ) { commandManager.closeLine( line ); }
int eraseAt( float x, float y, float zoom, EraseCommand cmd )
{ return commandManager.eraseAt( x, y, zoom, cmd ); }
void addEraseCommand( EraseCommand cmd )
{
isDrawing = true;
commandManager.addEraseCommand( cmd );
}
void clearReferences( int type )
{
if ( PlotInfo.isProfile( type ) ) {
mCommandManager2.clearReferences();
} else if ( type == PlotInfo.PLOT_PLAN ) {
mCommandManager1.clearReferences();
} else {
mCommandManager3.clearReferences();
}
}
void flipProfile( float z )
{
if ( mCommandManager2 == null ) return;
mCommandManager2.flipXAxis( z );
}
// static Handler previewDoneHandler = new Handler()
// {
// @Override
// public void handleMessage(Message msg) {
// isDrawing = false;
// }
// };
void refreshSurface()
{
// if ( mZoomer != null ) mZoomer.checkZoomBtnsCtrl();
Canvas canvas = null;
try {
canvas = mHolder.lockCanvas();
// canvas.drawColor(0, PorterDuff.Mode.CLEAR);
if ( canvas != null ) {
mWidth = canvas.getWidth();
mHeight = canvas.getHeight();
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll( canvas, mZoomer.zoom(), mSplayStations );
if ( previewPath != null ) previewPath.draw(canvas, null);
}
} finally {
if ( canvas != null ) {
mHolder.unlockCanvasAndPost( canvas );
}
}
}
// void clearDrawing() { commandManager.clearDrawing(); }
class DrawThread extends Thread
{
private SurfaceHolder mSurfaceHolder;
public DrawThread(SurfaceHolder surfaceHolder)
{
mSurfaceHolder = surfaceHolder;
}
public void setRunning(boolean run)
{
_run = run;
}
@Override
public void run()
{
while ( _run ) {
if ( isDrawing == true ) {
refreshSurface();
} else {
try {
// Log.v( TopoDroidApp.TAG, "drawing thread sleeps ..." );
sleep(100);
} catch ( InterruptedException e ) { }
}
}
}
}
// called by DrawingWindow::computeReference
public DrawingStationName addDrawingStationName ( NumStation num_st, float x, float y, boolean selectable, List<PlotInfo> xsections )
{
// TDLog.Log( TDLog.LOG_PLOT, "add Drawing Station Name " + num_st.name + " " + x + " " + y );
// FIXME STATION_XSECTION
// DO as when loaded
DrawingStationName st = new DrawingStationName( num_st, x, y );
if ( num_st.mHidden == 1 ) {
st.setPaint( BrushManager.fixedStationHiddenPaint );
} else if ( num_st.mHidden == -1 || num_st.mBarrierAndHidden ) {
st.setPaint( BrushManager.fixedStationBarrierPaint );
} else {
st.setPaint( BrushManager.fixedStationPaint );
}
if ( xsections != null ) {
for ( PlotInfo plot : xsections ) {
if ( plot.start.equals( st.mName ) ) {
st.setXSection( plot.azimuth, plot.clino, mType );
break;
}
}
}
commandManager.addStation( st, selectable ); // NOTE make this always true if you want station selectable on all sections
return st;
}
// called by DrawingWindow (for SECTION)
// note: not selectable
public DrawingStationName addDrawingStationName( String name, float x, float y )
{
// TDLog.Log( TDLog.LOG_PLOT, "add Drawing Station Name " + name + " " + x + " " + y );
// NOTE No station_XSection in X-Sections
DrawingStationName st = new DrawingStationName( name, x, y );
st.setPaint( BrushManager.fixedStationPaint );
commandManager.addStation( st, false ); // NOTE make this true for selectable station in all sections
return st;
}
void resetFixedPaint( Paint paint )
{
mCommandManager1.resetFixedPaint( paint );
mCommandManager2.resetFixedPaint( paint );
}
// called by DarwingActivity::addFixedLine
public void addFixedPath( DrawingPath path, boolean splay, boolean selectable )
{
if ( splay ) {
commandManager.addSplayPath( path, selectable );
} else {
commandManager.addLegPath( path, selectable );
}
// commandManager.addFixedPath( path, selectable );
}
public void setNorthPath( DrawingPath path ) { commandManager.setNorth( path ); }
public void setFirstReference( DrawingPath path ) { commandManager.setFirstReference( path ); }
public void setSecondReference( DrawingPath path ) { commandManager.setSecondReference( path ); }
// k : grid type 1, 10, 100
public void addGridPath( DrawingPath path, int k ) { commandManager.addGrid( path, k ); }
public void addDrawingPath (DrawingPath drawingPath) { commandManager.addCommand(drawingPath); }
public void deleteSectionPoint( String scrap_name )
{
commandManager.deleteSectionPoint( scrap_name, null ); // null eraseCommand
}
// void setBounds( float x1, float x2, float y1, float y2 ) { commandManager.setBounds( x1, x2, y1, y2 ); }
public boolean hasMoreRedo() { return commandManager.hasMoreRedo(); }
public void redo()
{
isDrawing = true;
commandManager.redo();
}
public void undo()
{
isDrawing = true;
commandManager.undo();
}
public boolean hasMoreUndo() { return commandManager.hasMoreUndo(); }
// public boolean hasStationName( String name ) { return commandManager.hasUserStation( name ); }
DrawingStationPath getStationPath( String name ) { return commandManager.getUserStation( name ); }
void addDrawingStationPath( DrawingStationPath path ) { commandManager.addUserStation( path ); }
void removeDrawingStationPath( DrawingStationPath path ) { commandManager.removeUserStation( path ); }
public RectF getBitmapBounds( ) { return commandManager.getBitmapBounds(); }
public float getBitmapScale() { return commandManager.getBitmapScale(); }
public Bitmap getBitmap( long type )
{
if ( PlotInfo.isProfile( type ) ) {
return mCommandManager2.getBitmap();
} else if ( type == PlotInfo.PLOT_PLAN ) {
return mCommandManager1.getBitmap();
} else {
return mCommandManager3.getBitmap();
}
}
// @param lp point
// @param type line type
// @param zoom canvas zoom
DrawingLinePath getLineToContinue( LinePoint lp, int type, float zoom )
{
return commandManager.getLineToContinue( lp, type, zoom );
}
/** add the points of the first line to the second line
*/
void addLineToLine( DrawingLinePath line, DrawingLinePath line0 ) { commandManager.addLineToLine( line, line0 ); }
// ---------------------------------------------------------------------
// SELECT - EDIT
// public SelectionPoint getPointAt( float x, float y ) { return commandManager.getPointAt( x, y ); }
// public SelectionPoint getLineAt( float x, float y ) { return commandManager.getLineAt( x, y ); }
// public SelectionPoint getAreaAt( float x, float y ) { return commandManager.getAreaAt( x, y ); }
// public SelectionPoint getShotAt( float x, float y ) { return commandManager.getShotAt( x, y ); }
// x,y canvas coords
DrawingStationName getStationAt( float x, float y ) { return commandManager.getStationAt( x, y ); }
SelectionSet getItemsAt( float x, float y, float zoom ) { return commandManager.getItemsAt( x, y, zoom ); }
// set line range at the hot-item
// type = range type
boolean setRangeAt( float x, float y, float zoom, int type ) { return commandManager.setRangeAt( x, y, zoom, type ); }
boolean moveHotItemToNearestPoint() { return commandManager.moveHotItemToNearestPoint(); }
int snapHotItemToNearestLine() { return commandManager.snapHotItemToNearestLine(); }
void splitHotItem() { commandManager.splitHotItem(); }
SelectionPoint hotItem() { return commandManager.hotItem(); }
// void shiftHotItem( float dx, float dy, float range ) { commandManager.shiftHotItem( dx, dy, range ); }
void shiftHotItem( float dx, float dy ) { commandManager.shiftHotItem( dx, dy ); }
void rotateHotItem( float dy ) { commandManager.rotateHotItem( dy ); }
SelectionPoint nextHotItem() { return commandManager.nextHotItem(); }
SelectionPoint prevHotItem() { return commandManager.prevHotItem(); }
void clearSelected() { commandManager.clearSelected(); }
void shiftDrawing( float x, float y ) { commandManager.shiftDrawing( x, y ); }
// ---------------------------------------------------------------------
public void surfaceChanged(SurfaceHolder mHolder, int format, int width, int height)
{
// TDLog.Log( TDLog.LOG_PLOT, "surfaceChanged " );
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder mHolder)
{
TDLog.Log( TDLog.LOG_PLOT, "surfaceCreated " );
if ( mDrawThread == null ) {
mDrawThread = new DrawThread(mHolder);
}
mDrawThread.setRunning(true);
mDrawThread.start();
mSurfaceCreated = true;
}
public void surfaceDestroyed(SurfaceHolder mHolder)
{
mSurfaceCreated = false;
TDLog.Log( TDLog.LOG_PLOT, "surfaceDestroyed " );
boolean retry = true;
mDrawThread.setRunning(false);
while (retry) {
try {
mDrawThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
mDrawThread = null;
}
public void exportTherion( // DataHelper dh, long sid,
int type, BufferedWriter out, String sketch_name, String plot_name, int proj_dir )
{
// Log.v("DistoX", sketch_name + " export th2 type " + type );
if ( PlotInfo.isProfile( type ) ) {
mCommandManager2.exportTherion( /* dh, sid, */ type, out, sketch_name, plot_name, proj_dir );
} else if ( type == PlotInfo.PLOT_PLAN ) {
mCommandManager1.exportTherion( /* dh, sid, */ type, out, sketch_name, plot_name, proj_dir );
} else {
mCommandManager3.exportTherion( /* dh, sid, */ type, out, sketch_name, plot_name, proj_dir );
}
}
public void exportDataStream( int type, DataOutputStream dos, String sketch_name, int proj_dir )
{
// Log.v("DistoX", sketch_name + " export stream type " + type );
if ( PlotInfo.isProfile( type ) ) {
mCommandManager2.exportDataStream( type, dos, sketch_name, proj_dir );
} else if ( type == PlotInfo.PLOT_PLAN ) {
mCommandManager1.exportDataStream( type, dos, sketch_name, 0 );
} else {
mCommandManager3.exportDataStream( type, dos, sketch_name, 0 );
}
}
private SymbolsPalette preparePalette()
{
SymbolsPalette palette = new SymbolsPalette();
// populate local palette with default symbols
palette.addPointFilename("user"); // make sure local palette contains "user" symnbols
palette.addLineFilename("user");
palette.addAreaFilename("user");
for ( Symbol p : BrushManager.mPointLib.getSymbols() ) if ( p.isEnabled() ) {
String fname = p.getFilename();
if ( ! fname.equals("user") ) palette.addPointFilename( fname );
}
for ( Symbol p : BrushManager.mLineLib.getSymbols() ) if ( p.isEnabled() ) {
String fname = p.getFilename();
if ( ! fname.equals("user") ) palette.addLineFilename( fname );
}
for ( Symbol p : BrushManager.mAreaLib.getSymbols() ) if ( p.isEnabled() ) {
String fname = p.getFilename();
if ( ! fname.equals("user") ) palette.addAreaFilename( fname );
}
return palette;
}
// -------------------------------------------------------------------
// LOAD
// called by OverviewWindow
// @pre th2 != null
public boolean addloadTherion( String th2, float xdelta, float ydelta, SymbolsPalette missingSymbols )
{
SymbolsPalette localPalette = preparePalette();
if ( (new File(th2)).exists() ) {
return DrawingIO.doLoadTherion( this, th2, xdelta, ydelta, missingSymbols, localPalette );
}
return false;
}
// called by OverviewWindow
// @pre tdr != null
public boolean addloadDataStream( String tdr, String th2, float xdelta, float ydelta, SymbolsPalette missingSymbols )
{
SymbolsPalette localPalette = preparePalette();
if ( (new File(tdr)).exists() ) {
return DrawingIO.doLoadDataStream( this, tdr, xdelta, ydelta, missingSymbols, localPalette, null, false );
} else if ( th2 != null && (new File(th2)).exists() ) {
return DrawingIO.doLoadTherion( this, th2, xdelta, ydelta, missingSymbols, localPalette );
}
return false;
}
// @note th21 and th22 can be null
public boolean modeloadTherion( String th21, SymbolsPalette missingSymbols )
{
SymbolsPalette localPalette = preparePalette();
if ( missingSymbols != null ) missingSymbols.resetSymbolLists();
return DrawingIO.doLoadTherion( this, th21, 0, 0, missingSymbols, localPalette );
}
// FIXME
// WITH VERSION 3.0 support for TH2 fallback will be dropped
// @note tdr1 and tdr2 can be null
// @note th21 and th22 can be null,
// @note th21 is not used if tdr1 == null
// @note th22 is not used if tdr2 == null
public boolean modeloadDataStream( String tdr1, String th21, SymbolsPalette missingSymbols )
{
SymbolsPalette localPalette = preparePalette();
if ( missingSymbols != null ) missingSymbols.resetSymbolLists();
if ( tdr1 != null ) {
if ( (new File( tdr1 )).exists() ) {
return DrawingIO.doLoadDataStream( this, tdr1, 0, 0, missingSymbols, localPalette, null, false );
} else if ( th21 != null && (new File(th21)).exists() ) {
return DrawingIO.doLoadTherion( this, th21, 0, 0, missingSymbols, localPalette );
}
}
return true;
}
// -----------------------------------------------------------------------------
// EXPORT
void exportAsCsx( PrintWriter pw, long type, String cave, String branch )
{
if ( PlotInfo.isProfile( type ) ) {
// FIXME OK PROFILE to check
mCommandManager2.exportAsCsx( pw, cave, branch );
} else if ( type == PlotInfo.PLOT_PLAN ) {
mCommandManager1.exportAsCsx( pw, cave, branch );
} else { // should never happen, but it happens for X-Sections
pw.format(" <layers>\n");
pw.format(" <layer name=\"Base\" type=\"0\">\n");
pw.format(" <items />\n");
pw.format(" </layer>\n");
pw.format(" <layer name=\"Soil\" type=\"1\">\n");
pw.format(" <items />\n");
pw.format(" </layer>\n");
pw.format(" <layer name=\"Water and floor morphologies\" type=\"2\">\n");
pw.format(" <items />\n");
pw.format(" </layer>\n");
pw.format(" <layer name=\"Rocks and concretions\" type=\"3\">\n");
pw.format(" <items />\n");
pw.format(" </layer>\n");
pw.format(" <layer name=\"Ceiling morphologies\" type=\"4\">\n");
pw.format(" <items />\n");
pw.format(" </layer>\n");
pw.format(" <layer name=\"Borders\" type=\"5\">\n");
pw.format(" <items>\n");
// pw.format(" <item layer=\"5\" name=\"Esempio bezier\" type=\"4\" category=\"1\" linetype=\"2\" mergemode=\"0\">\n");
// pw.format(" <pen type="1" />
// pw.format(" <points data="-6.69 1.04 B -6.51 1.58 -5.85 2.21 -5.04 2.63 -3.81 2.93 -1.56 2.57 -0.45 2.06 0.00 1.46 0.87 1.31 1.20 -0.17 1.29 -1.13 1.17 -2.24 0.93 -2.75 0.18 -4.85 1.83 -5.09 2.76 -5.78 3.21 -5.93 " />
// pw.format(" </item>
// pw.format(" <item layer="5" name="Esempio spline" type="4" category="1" linetype="1" mergemode="0">
// pw.format(" <pen type="1" />
// pw.format(" <points data="-3.30 6.26 B -3.12 6.80 -2.46 7.43 -1.65 7.85 -0.42 8.15 1.83 7.79 2.94 7.28 3.39 6.68 4.26 6.53 4.68 5.08 4.68 4.09 4.56 2.98 4.32 2.47 3.57 0.37 5.22 0.13 6.15 -0.56 6.60 -0.71 " />
// pw.format(" </item>
// pw.format(" <item layer="5" name="Esempio rette" type="4" category="1" linetype="0" mergemode="0">
// pw.format(" <pen type="1" />
// pw.format(" <points data="-9.60 -3.47 B -8.97 -2.81 -7.71 -2.27 -6.45 -2.21 -4.92 -2.75 -4.38 -3.11 -3.69 -3.92 -3.45 -4.70 -3.36 -6.80 -2.79 -8.06 -2.34 -8.39 -0.42 -8.93 " />
// pw.format(" </item>
pw.format(" </items>\n");
pw.format(" </layer>\n");
pw.format(" <layer name=\"Signs\" type=\"6\">\n");
pw.format(" <items />\n");
pw.format(" </layer>\n");
pw.format(" </layers>\n");
}
}
// void addSplayStation( String station )
// {
// if ( station == null ) return;
// if ( mSplayStations.contains( station ) ) return;
// mSplayStations.add( station );
// }
// void removeSplayStation( String station )
// {
// if ( station == null ) return;
// // if ( ! mSplayStations.contains( station ) ) return;
// mSplayStations.remove( station );
// }
void toggleStationSplays( String station )
{
if ( station == null ) return;
if ( mSplayStations.contains( station ) ) {
mSplayStations.remove( station );
} else {
mSplayStations.add( station );
}
}
void setStationSplays( String station, boolean on )
{
if ( station == null ) return;
if ( mSplayStations.contains( station ) ) {
if ( ! on ) mSplayStations.remove( station );
} else {
if ( on ) mSplayStations.add( station );
}
}
void setStationXSections( List<PlotInfo> xsection_plan, List<PlotInfo> xsection_ext, long type2 )
{
mCommandManager1.setStationXSections( xsection_plan, PlotInfo.PLOT_PLAN );
mCommandManager2.setStationXSections( xsection_ext, type2 );
}
// only for sections
float computeSectionArea()
{
return commandManager.computeSectionArea();
}
void deleteSectionLine( DrawingLinePath line, String scrap )
{
isDrawing = true;
EraseCommand cmd = new EraseCommand();
commandManager.deleteSectionLine( line, scrap, cmd );
commandManager.deleteSectionPoint( scrap, cmd );
commandManager.addEraseCommand( cmd );
}
}