package de.blau.android.resources;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeMap;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Typeface;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import de.blau.android.R;
import de.blau.android.contract.Paths;
import de.blau.android.resources.DataStyle.FeatureStyle.DashPath;
import de.blau.android.util.Density;
import de.blau.android.util.FileUtil;
public class DataStyle extends DefaultHandler {
private final static String FILE_PATH_STYLE_SUFFIX = "-profile.xml";
// constants for the internal profiles
public final static String GPS_TRACK = "gps_track";
public final static String INFOTEXT = "infotext";
public final static String ATTRIBUTION_TEXT = "attribution_text";
public final static String VIEWBOX = "viewbox";
public final static String WAY_TOLERANCE = "way_tolerance";
public final static String WAY_TOLERANCE_2 = "way_tolerance_2";
public final static String WAY = "way";
public final static String SELECTED_WAY = "selected_way";
public final static String SELECTED_RELATION_WAY = "selected_relation_way";
public final static String PROBLEM_WAY = "problem_way";
public final static String HIDDEN_WAY = "hidden_way";
public final static String NODE_TOLERANCE = "node_tolerance";
public final static String NODE_TOLERANCE_2 = "node_tolerance_2";
public final static String NODE = "node";
public final static String NODE_THIN = "node_thin";
public static final String NODE_TAGGED = "node_tagged";
public static final String NODE_DRAG_RADIUS = "node_drag_radius";
public final static String PROBLEM_NODE = "problem_node";
public final static String PROBLEM_NODE_THIN = "problem_node_thin";
public static final String PROBLEM_NODE_TAGGED = "problem_node_tagged";
public final static String SELECTED_NODE = "selected_node";
public final static String SELECTED_NODE_THIN = "selected_node_thin";
public static final String SELECTED_NODE_TAGGED = "selected_node_tagged";
public final static String SELECTED_RELATION_NODE = "selected_relation_node";
public final static String SELECTED_RELATION_NODE_THIN = "selected_relation_node_thin";
public static final String SELECTED_RELATION_NODE_TAGGED = "selected_relation_node_tagged";
public final static String HIDDEN_NODE = "hidden_node";
public final static String WAY_DIRECTION = "way_direction";
public final static String ONEWAY_DIRECTION = "oneway_direction";
public final static String LARGE_DRAG_AREA = "large_drag_area";
public final static String MARKER_SCALE = "marker_scale";
public final static String GPS_POS = "gps_pos";
public final static String GPS_POS_FOLLOW = "gps_pos_follow";
public final static String GPS_ACCURACY = "gps_accuracy";
public final static String OPEN_NOTE = "open_note";
public final static String CLOSED_NOTE = "closed_note";
public final static String CROSSHAIRS = "crosshairs";
public final static String CROSSHAIRS_HALO = "crosshairs_halo";
public final static String HANDLE = "handle";
public final static String LABELTEXT = "labeltext";
public class FeatureStyle {
String name;
boolean editable;
boolean internal;
boolean updateWidth;
Paint paint;
float widthFactor;
DashPath dashPath = null;
public class DashPath {
public float[] intervals;
public float phase;
}
FeatureStyle (String n, Paint p) {
// Log.i("FeatureStyle","setting up feature " + n);
name = n;
editable = true;
internal = false;
updateWidth = true;
if (p != null) {
paint = new Paint(p);
}
else {
paint = new Paint();
}
widthFactor = 1.0f;
}
FeatureStyle (String n) {
this(n, (Paint)null);
}
FeatureStyle(String n, FeatureStyle fp)
{
if (fp == null) {
Log.i("FeatureStyle","setting up feature " + n + " profile is null!");
return;
}
// Log.i("FeatureStyle","setting up feature " + n + " from " + fp.getName());
name = n;
editable = fp.editable;
internal = fp.internal;
updateWidth = fp.updateWidth;
paint = new Paint(fp.paint);
widthFactor = fp.widthFactor;
if (fp.dashPath != null) {
dashPath = new DashPath();
dashPath.intervals = fp.dashPath.intervals.clone();
dashPath.phase = fp.dashPath.phase;
}
}
public String getName() {
return name;
}
public Paint getPaint() {
return paint;
}
public void setColor(int c)
{
paint.setColor(c);
}
public void setWidthFactor(float f)
{
widthFactor = f;
}
public float getWidthFactor()
{
return widthFactor;
}
public void setStrokeWidth(float width) {
if (updateWidth) {
paint.setStrokeWidth(width*widthFactor);
if (dashPath != null) {
float[] intervals = dashPath.intervals.clone();
for (int i=0;i<intervals.length;i++) {
intervals[i] = dashPath.intervals[i] * width * widthFactor;
}
DashPathEffect dp = new DashPathEffect(intervals, dashPath.phase);
paint.setPathEffect(dp);
}
}
}
public void dontUpdate() {
updateWidth = false;
}
public boolean updateWidth() {
return updateWidth;
}
public boolean isEditable() {
return editable;
}
public void setEditable(boolean e) {
editable = e;
}
public DashPath getDashPath() {
return dashPath;
}
public void setDashPath(float[] i, float p) {
dashPath= new DashPath();
dashPath.intervals = i;
dashPath.phase = p;
}
public boolean isInternal() {
return internal;
}
public void setInternal(boolean i) {
internal = i;
}
}
private String name;
private HashMap<String,FeatureStyle> featureStyles;
private static DataStyle currentStyle;
private static HashMap<String,DataStyle> availableStyles = new HashMap<String,DataStyle>();
public static final float NODE_OVERLAP_TOLERANCE_VALUE = 10f;
private static final int TOLERANCE_ALPHA = 40;
private static final int TOLERANCE_ALPHA_2 = 128;
/**
* GPS arrow
*/
public Path orientation_path = new Path();
/**
* Crosshairs
*/
public Path crosshairs_path = new Path();
/**
* X
*/
public Path x_path = new Path();
/**
* Arrow indicating the direction of one-way streets. Set/updated in updateStrokes
*/
public static final Path WAY_DIRECTION_PATH = new Path();
private static final String BUILTIN_STYLE_NAME = "Default";
public float nodeToleranceValue;
public float wayToleranceValue;
private float largDragCircleRadius;
public float largDragToleranceRadius;
public float minLenForHandle;
private final Context ctx;
private DataStyle(final Context ctx) {
this.ctx = ctx;
// create default
init(ctx.getResources());
}
public DataStyle(String n, DataStyle from) {
// copy existing profile
this.ctx = from.ctx;
name = n;
featureStyles = new HashMap<String, FeatureStyle>();
for (FeatureStyle fp : from.featureStyles.values()) {
featureStyles.put(fp.getName(),new FeatureStyle(fp.getName(), fp));
}
}
private DataStyle(Context ctx, InputStream is) {
this.ctx = ctx;
// create a profile from a file
init(ctx.getResources()); // defaults for internal styles
read(is);
}
/**
* initialize the minimum required internal style for a new profile
* @param resources
*/
private void init(final Resources resources) {
nodeToleranceValue = Density.dpToPx(ctx,40f); // TODO move to constant
wayToleranceValue = Density.dpToPx(ctx,40f);
largDragCircleRadius = Density.dpToPx(ctx,70f);
largDragToleranceRadius = Density.dpToPx(ctx,100f);
minLenForHandle = 5 * nodeToleranceValue;
orientation_path.moveTo(0, Density.dpToPx(ctx,-20));
orientation_path.lineTo(Density.dpToPx(ctx,15), Density.dpToPx(ctx,20));
orientation_path.lineTo(0, Density.dpToPx(ctx,10));
orientation_path.lineTo(Density.dpToPx(ctx,-15), Density.dpToPx(ctx,20));
orientation_path.lineTo(0, Density.dpToPx(ctx,-20));
int arm = Density.dpToPx(ctx,10);
crosshairs_path.moveTo(0, -arm);
crosshairs_path.lineTo(0, arm);
crosshairs_path.moveTo(arm, 0);
crosshairs_path.lineTo(-arm, 0);
arm = Density.dpToPx(ctx,4);
x_path.moveTo(-arm, -arm);
x_path.lineTo(arm, arm);
x_path.moveTo(arm, -arm);
x_path.lineTo(-arm, arm);
PorterDuffXfermode pixelOp = new PorterDuffXfermode(PorterDuff.Mode.XOR);
Log.i("Style","setting up default profile elements");
featureStyles = new HashMap<String, FeatureStyle>();
Paint standardPath = new Paint();
standardPath.setStyle(Style.STROKE);
// As nodes cover the line ends/joins, the line ending styles are irrelevant for most paints
// However, at least on the software renderer, the default styles (Cap = BUTT, Join = MITER)
// have slightly better performance than the round styles.
FeatureStyle fp = new FeatureStyle(WAY, standardPath);
fp.setColor(Color.BLACK);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(PROBLEM_WAY, standardPath);
fp.setColor(ContextCompat.getColor(ctx,R.color.problem));
fp.setWidthFactor(1.5f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(VIEWBOX, standardPath);
fp.setColor(ContextCompat.getColor(ctx,R.color.grey));
fp.dontUpdate();
fp.getPaint().setStyle(Style.FILL);
fp.getPaint().setAlpha(125);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(HANDLE);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_red));
fp.setWidthFactor(1f);
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,1.0f));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(NODE);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_red));
fp.setWidthFactor(1f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(NODE_TAGGED);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_red));
fp.setWidthFactor(1.5f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(NODE_THIN);
fp.dontUpdate();
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,1.0f));
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_red));
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(PROBLEM_NODE);
fp.setColor(ContextCompat.getColor(ctx,R.color.problem));
fp.setWidthFactor(1f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(PROBLEM_NODE_TAGGED);
fp.setColor(ContextCompat.getColor(ctx,R.color.problem));
fp.setWidthFactor(1.5f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(PROBLEM_NODE_THIN);
fp.dontUpdate();
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,1.0f));
fp.setColor(ContextCompat.getColor(ctx,R.color.problem));
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(HIDDEN_NODE);
fp.dontUpdate();
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,1.0f));
fp.setColor(ContextCompat.getColor(ctx,R.color.light_grey));
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(GPS_TRACK,featureStyles.get(WAY));
fp.setColor(Color.BLUE);
fp.getPaint().setStrokeCap(Cap.ROUND);
fp.getPaint().setStrokeJoin(Join.ROUND);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(WAY_TOLERANCE,featureStyles.get(WAY));
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_ocher));
fp.dontUpdate();
fp.getPaint().setAlpha(TOLERANCE_ALPHA);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,wayToleranceValue));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(WAY_TOLERANCE_2,featureStyles.get(WAY));
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_ocher));
fp.dontUpdate();
fp.getPaint().setAlpha(TOLERANCE_ALPHA_2);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,wayToleranceValue));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_NODE);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_beige));
fp.setWidthFactor(1.5f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_RELATION_NODE, featureStyles.get(SELECTED_NODE));
fp.setColor(ContextCompat.getColor(ctx,R.color.relation));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(NODE_DRAG_RADIUS );
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_beige));
fp.dontUpdate();
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setAlpha(150);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,10f));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_NODE_TAGGED);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_beige));
fp.setWidthFactor(2f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_RELATION_NODE_TAGGED, featureStyles.get(SELECTED_NODE_TAGGED));
fp.setColor(ContextCompat.getColor(ctx,R.color.relation));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_NODE_THIN);
fp.dontUpdate();
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,1.0f));
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_beige));
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_RELATION_NODE_THIN, featureStyles.get(SELECTED_NODE_THIN));
fp.setColor(ContextCompat.getColor(ctx,R.color.relation));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(GPS_POS,featureStyles.get(GPS_TRACK));
fp.getPaint().setStyle(Style.FILL);
fp.setWidthFactor(2f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(GPS_POS_FOLLOW,featureStyles.get(GPS_POS));
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,4.0f));
fp.dontUpdate();
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(GPS_ACCURACY,featureStyles.get(GPS_POS));
fp.getPaint().setStyle(Style.FILL_AND_STROKE);
fp.getPaint().setAlpha(TOLERANCE_ALPHA);
fp.dontUpdate();
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_WAY,featureStyles.get(WAY));
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_beige));
fp.setWidthFactor(2f);
fp.getPaint().setStrokeCap(Cap.ROUND);
fp.getPaint().setStrokeJoin(Join.ROUND);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(HIDDEN_WAY,featureStyles.get(WAY));
fp.setColor(ContextCompat.getColor(ctx,R.color.light_grey));
fp.getPaint().setAlpha(TOLERANCE_ALPHA);
fp.setWidthFactor(0.5f);
fp.getPaint().setStrokeCap(Cap.ROUND);
fp.getPaint().setStrokeJoin(Join.ROUND);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(SELECTED_RELATION_WAY,featureStyles.get(SELECTED_WAY));
fp.setColor(ContextCompat.getColor(ctx,R.color.relation));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(NODE_TOLERANCE);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_ocher));
fp.dontUpdate();
fp.getPaint().setStyle(Style.FILL);
fp.getPaint().setAlpha(TOLERANCE_ALPHA);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,nodeToleranceValue));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(NODE_TOLERANCE_2);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_ocher));
fp.dontUpdate();
fp.getPaint().setStyle(Style.FILL);
fp.getPaint().setAlpha(TOLERANCE_ALPHA_2);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,nodeToleranceValue));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(INFOTEXT);
fp.setColor(Color.BLACK);
fp.dontUpdate();
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(ATTRIBUTION_TEXT);
fp.setColor(Color.WHITE);
fp.dontUpdate();
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
fp.getPaint().setShadowLayer(1, 0, 0, Color.BLACK);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(LABELTEXT);
fp.setColor(Color.BLACK);
fp.dontUpdate();
fp.getPaint().setTypeface(Typeface.SANS_SERIF);
fp.getPaint().setTextSize(Density.dpToPx(ctx,12));
fp.getPaint().setXfermode(pixelOp);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(WAY_DIRECTION);
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_red));
fp.setWidthFactor(0.8f);
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setStrokeCap(Cap.SQUARE);
fp.getPaint().setStrokeJoin(Join.MITER);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(ONEWAY_DIRECTION,featureStyles.get(WAY_DIRECTION));
fp.setColor(ContextCompat.getColor(ctx,R.color.ccc_blue));
fp.setWidthFactor(0.5f);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(OPEN_NOTE);
fp.setColor(ContextCompat.getColor(ctx,R.color.bug_open));
fp.getPaint().setAlpha(100);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(CLOSED_NOTE);
fp.setColor(ContextCompat.getColor(ctx,R.color.bug_closed));
fp.getPaint().setAlpha(100);
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(CROSSHAIRS);
fp.setColor(Color.BLACK);
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,1.0f));
// fp.getPaint().setXfermode(pixelOp);
fp.dontUpdate();
featureStyles.put(fp.getName(), fp);
fp = new FeatureStyle(CROSSHAIRS_HALO);
fp.setColor(Color.WHITE);
fp.getPaint().setStyle(Style.STROKE);
fp.getPaint().setStrokeWidth(Density.dpToPx(ctx,3.0f));
// fp.getPaint().setXfermode(pixelOp);
fp.dontUpdate();
featureStyles.put(fp.getName(), fp);
Log.i("Style","... done");
}
public static void setAntiAliasing(final boolean aa) {
for (FeatureStyle fp : currentStyle.featureStyles.values()) {
fp.getPaint().setAntiAlias(aa);
}
}
/**
* Sets the stroke width of all Elements corresponding to the width of the viewbox (=zoomfactor).
*/
public static void updateStrokes(final float newStrokeWidth) {
for (FeatureStyle fp : currentStyle.featureStyles.values()) {
fp.setStrokeWidth(newStrokeWidth);
}
// hardwired (for now)
WAY_DIRECTION_PATH.rewind();
float wayDirectionPathOffset = newStrokeWidth * 2.0f;
WAY_DIRECTION_PATH.moveTo(-wayDirectionPathOffset, -wayDirectionPathOffset);
WAY_DIRECTION_PATH.lineTo(0,0);
WAY_DIRECTION_PATH.lineTo(-wayDirectionPathOffset, +wayDirectionPathOffset);
}
/**
* get FeatureStyle specified by key from current profile
* @param key
* @return
*/
public static FeatureStyle getCurrent(final String key) {
return currentStyle.featureStyles.get(key);
}
/**
*
* @return
*/
public static DataStyle getCurrent() {
return currentStyle;
}
/**
* get FeatureStyle specified by key from this profile
* @param key
* @return
*/
public FeatureStyle get(final String key) {
return featureStyles.get(key);
}
/**
* return specific named profile
* @param n
* @return
*/
public static DataStyle getStyle(String n) {
if (availableStyles == null)
return null;
return availableStyles.get(n);
}
/**
* return list of available Styles (Defaut entry first, rest sorted)
* @return
*/
public static String[] getStyleList(Activity activity) {
if (availableStyles.size() == 0) { // shouldn't happen
Log.e("Style","getStyleList called before initialized");
addDefaultStye(activity);
}
// creating the default style object will set availableStyles
String[] res = new String[availableStyles.size()];
res[0] = BUILTIN_STYLE_NAME;
String keys[] = (new TreeMap<String, DataStyle>(availableStyles)).keySet().toArray(new String[0]); // sort the list
int j = 1;
for (int i=0;i<res.length;i++) {
if (!keys[i].equals(BUILTIN_STYLE_NAME)) {
res[j] = keys[i];
j++;
}
} // probably silly way of doing this
return res;
}
/**
* switch to Style with name n
* @param n
* @return
*/
public static boolean switchTo(String n) {
DataStyle p = getStyle(n);
if (p != null) {
currentStyle = p;
Log.i("Style","Switching to " + n);
return true;
}
return false;
}
/**
* dump this Style in XML format, not very abstracted and closely tied to the implementation
* @param s
* @throws IllegalArgumentException
* @throws IllegalStateException
* @throws IOException
*/
private void toXml(final XmlSerializer s) throws IllegalArgumentException,
IllegalStateException, IOException {
s.startTag("", "profile");
s.attribute("", "name", name);
for (FeatureStyle fp : featureStyles.values()) {
if (fp != null) {
s.startTag("", "feature");
s.attribute("", "name", fp.getName());
s.attribute("", "internal", Boolean.toString(fp.isInternal()));
boolean updateWidth = fp.updateWidth();
s.attribute("", "updateWidth", Boolean.toString(updateWidth));
s.attribute("", "widthFactor", Float.toString(fp.getWidthFactor()));
s.attribute("", "editable", Boolean.toString(fp.isEditable()));
//
s.attribute("", "color", Integer.toHexString(fp.getPaint().getColor()));
// alpha should be contained in color
s.attribute("", "style", fp.getPaint().getStyle().toString());
s.attribute("", "cap", fp.getPaint().getStrokeCap().toString());
s.attribute("", "join", fp.getPaint().getStrokeJoin().toString());
if (!updateWidth) {
s.attribute("", "strokewidth", Float.toString(fp.getPaint().getStrokeWidth()));
}
Typeface tf = fp.getPaint().getTypeface();
if (tf != null) {
s.attribute("", "typefacestyle", Integer.toString(tf.getStyle()));
s.attribute("", "textsize", Float.toString(fp.getPaint().getTextSize()));
}
DashPath dp = fp.getDashPath();
if (dp != null) {
s.startTag("", "dash");
s.attribute("", "phase",Float.toString(dp.phase));
for (int i=0;i<dp.intervals.length;i++) {
s.startTag("", "interval");
s.attribute("", "length",Float.toString(dp.intervals[i]));
s.endTag("", "interval");
}
s.endTag("", "dash");
}
s.endTag("", "feature");
}
else {
Log.d("Style", "null fp");
}
}
s.endTag("", "profile");
}
/**
* save this profile to SDCARD
*/
void save() {
String filename = name + FILE_PATH_STYLE_SUFFIX;
OutputStream outputStream = null;
try {
File outDir = FileUtil.getPublicDirectory();
File outfile = new File(outDir, filename);
outputStream = new BufferedOutputStream(new FileOutputStream(outfile));
XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
serializer.setOutput(outputStream, "UTF-8");
serializer.startDocument("UTF-8", null);
this.toXml(serializer);
serializer.endDocument();
} catch (Exception e) {
Log.e("Style", "Save failed - " + filename + " " + e);
} finally {
try {outputStream.close();} catch (Exception ex) {}
}
}
/**
* start parsing a config file
* @param in
* @throws SAXException
* @throws IOException
* @throws ParserConfigurationException
*/
private void start(final InputStream in) throws SAXException, IOException, ParserConfigurationException {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(in, this);
}
/**
* vars for the XML parser
*/
private FeatureStyle tempFeatureStyle;
private ArrayList<Float> tempIntervals;
private float tempPhase;
@Override
public void startElement(final String uri, final String element, final String qName, final Attributes atts) {
try {
if (element.equals("profile")) {
name = atts.getValue("name");
if (featureStyles == null) {
Log.i("Style","Allocating new list of feature profiles for profile " + name);
featureStyles = new HashMap<String, FeatureStyle>();
}
} else if (element.equals("feature")) {
// Log.i("Style", atts.getLength() + " attributes");
// for (int i=0;i<atts.getLength();i++) {
// Log.i("Style",atts.getLocalName(i) + "=" + atts.getValue(i));
// }
tempFeatureStyle = new FeatureStyle(atts.getValue("name"));
if (tempFeatureStyle.name.equals(LARGE_DRAG_AREA)) {
// special handling
largDragCircleRadius = Density.dpToPx(ctx,Float.parseFloat(atts.getValue("radius")));
largDragToleranceRadius = Density.dpToPx(ctx,Float.parseFloat(atts.getValue("touchRadius")));
return;
}
if (tempFeatureStyle.name.equals(MARKER_SCALE)) {
float scale = Float.parseFloat(atts.getValue("scale"));
orientation_path = new Path();
orientation_path.moveTo(0, Density.dpToPx(ctx,-20) *scale);
orientation_path.lineTo(Density.dpToPx(ctx,15) *scale, Density.dpToPx(ctx,20) *scale);
orientation_path.lineTo(0, Density.dpToPx(ctx,10) *scale);
orientation_path.lineTo(Density.dpToPx(ctx,-15) *scale, Density.dpToPx(ctx,20) *scale);
orientation_path.lineTo(0, Density.dpToPx(ctx,-20) *scale);
crosshairs_path = new Path();
int arm = (int) Density.dpToPx(ctx,10*scale);
crosshairs_path.moveTo(0, -arm);
crosshairs_path.lineTo(0, arm);
crosshairs_path.moveTo(arm, 0);
crosshairs_path.lineTo(-arm, 0);
x_path = new Path();
arm = (int) Density.dpToPx(ctx,3*scale);
x_path.moveTo(-arm, -arm);
x_path.lineTo(arm, arm);
x_path.moveTo(arm, -arm);
x_path.lineTo(-arm, arm);
return;
}
tempFeatureStyle.setInternal(Boolean.valueOf(atts.getValue("internal")).booleanValue());
if (!Boolean.valueOf(atts.getValue("updateWidth")).booleanValue()) {
tempFeatureStyle.dontUpdate();
}
tempFeatureStyle.setWidthFactor(Float.parseFloat(atts.getValue("widthFactor")));
tempFeatureStyle.setEditable(Boolean.valueOf(atts.getValue("editable")).booleanValue());
tempFeatureStyle.setColor((int)Long.parseLong(atts.getValue("color"),16)); // workaround highest bit set problem
Style style = Style.valueOf(atts.getValue("style"));
tempFeatureStyle.getPaint().setStyle(style);
if (style != Style.STROKE && tempFeatureStyle.getName().startsWith("way-")) { // hack for filled polygons
tempFeatureStyle.getPaint().setAlpha(125);
}
tempFeatureStyle.getPaint().setStrokeCap(Cap.valueOf(atts.getValue("cap")));
tempFeatureStyle.getPaint().setStrokeJoin(Join.valueOf(atts.getValue("join")));
if (!tempFeatureStyle.updateWidth()) {
float strokeWidth = Density.dpToPx(ctx,Float.parseFloat(atts.getValue("strokewidth")));
tempFeatureStyle.getPaint().setStrokeWidth(strokeWidth);
// special case if we are setting internal tolerance values
if (tempFeatureStyle.name.equals(NODE_TOLERANCE)) {
nodeToleranceValue = strokeWidth;
} else if (tempFeatureStyle.name.equals(WAY_TOLERANCE)) {
wayToleranceValue = strokeWidth;
}
}
if (atts.getValue("typefacestyle") != null) {
tempFeatureStyle.getPaint().setTypeface(Typeface.defaultFromStyle(Integer.parseInt(atts.getValue("typefacestyle"))));
tempFeatureStyle.getPaint().setTextSize(Density.dpToPx(ctx,Float.parseFloat(atts.getValue("textsize"))));
if (atts.getValue("shadow") != null)
tempFeatureStyle.getPaint().setShadowLayer(Integer.parseInt(atts.getValue("shadow")), 0, 0, Color.BLACK);
}
// Log.i("Style","startElement finished parsing feature");
} else if (element.equals("dash")) {
tempPhase = Float.parseFloat(atts.getValue("phase"));
tempIntervals = new ArrayList<Float>();
} else if (element.equals("interval")) {
tempIntervals.add(Float.valueOf(Float.parseFloat(atts.getValue("length"))));
}
} catch (Exception e) {
Log.e("Profil", "Parse Exception", e);
}
}
@Override
public void endElement(final String uri, final String element, final String qName) {
if (element == null) {Log.i("Style","element is null"); return;}
//noinspection StatementWithEmptyBody
if (element.equals("profile")) {
} else if (element.equals("feature")) {
if (tempFeatureStyle == null) {
Log.i("Style","FeatureStyle is null");
return;
}
if (tempFeatureStyle.getName() == null) {
Log.i("Style","FeatureStyle name is null");
return;
}
// overwrites existing profiles
featureStyles.put(tempFeatureStyle.getName(),tempFeatureStyle);
// Log.i("Style","adding to list of features");
} else if (element.equals("dash")) {
float[] tIntervals = new float[tempIntervals.size()];
for (int i=0; i<tIntervals.length;i++) {tIntervals[i] = tempIntervals.get(i).floatValue();}
tempFeatureStyle.setDashPath(tIntervals, tempPhase);
} else //noinspection StatementWithEmptyBody
if (element.equals("interval")) {
}
}
private boolean read(InputStream is) {
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(is);
start(inputStream);
} catch (Exception e) {
Log.e("Style", "Read failed " + e);
e.printStackTrace();
}
return true;
}
/**
* searches directories for profile files and creates new profiles from them
* @param ctx
*/
public static void getStylesFromFiles(Context ctx) {
if (availableStyles.size()==0) {
Log.i("Style","No style files found");
// no files, need to install a default
addDefaultStye(ctx);
}
// assets directory
AssetManager assetManager = ctx.getAssets();
//
try {
String[] fileList = assetManager.list("");
if (fileList != null) {
for (String fn:fileList) {
if (fn.endsWith(FILE_PATH_STYLE_SUFFIX)) {
Log.i("Style","Creating profile from file in assets directory " + fn);
InputStream is = assetManager.open(fn);
DataStyle p = new DataStyle(ctx, is);
availableStyles.put(p.name,p);
}
}
}
} catch (Exception ex) { Log.i("Style", ex.toString());}
// from sdcard
File sdcard = Environment.getExternalStorageDirectory();
File indir = new File(sdcard, Paths.DIRECTORY_PATH_VESPUCCI);
if (indir != null) {
class StyleFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(FILE_PATH_STYLE_SUFFIX);
}
}
File[] list = indir.listFiles(new StyleFilter());
if (list != null) {
for (File f:list) {
Log.i("Style","Creating profile from " + f.getName());
try {
InputStream is = new FileInputStream(f);
DataStyle p = new DataStyle(ctx, is);
// overwrites profile with same name
availableStyles.put(p.name,p);
} catch (Exception ex) { Log.i("Style", ex.toString());}
}
}
}
}
private static void addDefaultStye(Context ctx) {
DataStyle p = new DataStyle(ctx);
p.name = BUILTIN_STYLE_NAME;
currentStyle = p;
availableStyles.put(p.name,p);
}
public static String getBuiltinStyleName() {
return BUILTIN_STYLE_NAME;
}
}