/*
* LocationController.java - Copyright(c) 2013 Joe Pasqua
* Provided under the MIT License. See the LICENSE file for details.
* Created: Jul 22, 2013
*/
package org.noroomattheinn.visibletesla;
import java.io.File;
import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.PauseTransition;
import javafx.animation.ScaleTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.util.Duration;
import org.apache.commons.io.FileUtils;
import org.noroomattheinn.fxextensions.MultiGauge;
import org.noroomattheinn.tesla.StreamState;
import org.noroomattheinn.utils.SimpleTemplate;
import org.noroomattheinn.utils.Utils;
import static org.noroomattheinn.tesla.Tesla.logger;
public class LocationController extends BaseController {
/*------------------------------------------------------------------------------
*
* Constants and Enums
*
*----------------------------------------------------------------------------*/
private static final String MapTemplateFileName = "MapTemplate.html";
private static final String SimpleMapTemplate = "SimpleMap.html";
/*------------------------------------------------------------------------------
*
* Internal State
*
*----------------------------------------------------------------------------*/
private boolean mapIsLoaded = false;
private WebEngine engine;
private Animation blipAnimation = null;
private boolean useMiles = true;
/*------------------------------------------------------------------------------
*
* UI Elements
*
*----------------------------------------------------------------------------*/
@FXML private WebView webView;
@FXML private ImageView loadingImage;
@FXML private Label loadingImageLabel;
private MultiGauge multigauge;
/*------------------------------------------------------------------------------
*
* UI Action Handlers
*
*----------------------------------------------------------------------------*/
@FXML void launchButtonHandler(ActionEvent event) {
// TITLE, ZOOM, LAT, LNG
StreamState state = vtVehicle.streamState.get();
if (state == null) return;
showSimpleMap(state.estLat, state.estLng, "Tesla", 18);
}
/*------------------------------------------------------------------------------
*
* Methods overridden from BaseController
*
*----------------------------------------------------------------------------*/
@Override protected void fxInitialize() {
engine = webView.getEngine();
progressIndicator.setVisible(false);
multigauge = new MultiGauge(25, 8, 0, 100, -60, 320);
multigauge.useGradient(Side.RIGHT, Color.DARKORANGE, Color.GREEN);
multigauge.useGradient(Side.LEFT, Color.BLUE, Color.BLUE);
multigauge.setLogScale(Side.RIGHT, true);
Node mg = multigauge.getContainer();
AnchorPane.setTopAnchor(mg, 25.0);
AnchorPane.setRightAnchor(mg, 10.0);
root.getChildren().add(2, mg);
multigauge.setVal(Side.LEFT, 20);
multigauge.setVal(Side.RIGHT, 40);
}
@Override protected void refresh() {
vtData.produceStream(false);
}
@Override protected void initializeState() {
useMiles = vtVehicle.unitType() == Utils.UnitType.Imperial;
blipAnimation = animateBlip();
vtData.produceStream(false);
vtVehicle.streamState.addTracker(new Runnable() {
@Override public void run() {
doUpdateLater(vtVehicle.streamState.get());
}
});
StreamState ss = vtVehicle.streamState.get();
if (ss != null) { doUpdateLater(ss); }
if (!useMiles) { multigauge.setRange(Side.LEFT, 0, 160); }
}
@Override protected void activateTab() { }
private void doUpdateLater(final StreamState state) {
Platform.runLater(new Runnable() {
@Override public void run() {
reflectInternal(state);
} });
}
/*------------------------------------------------------------------------------
*
* Private Utility Methods and Classes
*
*----------------------------------------------------------------------------*/
private void reflectInternal(StreamState ss) {
String latitude = String.valueOf(ss.estLat);
String longitude = String.valueOf(ss.estLng);
String heading = String.valueOf(ss.estHeading);
if (mapIsLoaded) {
try {
engine.executeScript(String.format(
"moveMarker(%s, %s, %s)", latitude, longitude, heading));
} catch (Exception e) {
logger.warning(e.toString());
}
} else {
String mapHTML = getMapFromTemplate(latitude, longitude, heading);
engine.getLoadWorker().stateProperty().addListener( new ChangeListener<State>() {
@Override public void changed(ObservableValue ov, State old, State cur) {
if (cur == State.SUCCEEDED) {
mapIsLoaded = true;
stopBlip(blipAnimation);
loadingImage.setVisible(false);
loadingImageLabel.setVisible(false);
}
}
});
engine.loadContent(mapHTML);
// This code can be used to communicate events back from javascript in
// the html code to this Java code. The javascript code would do something
// like: window.status = "load complete"; and that message would be passed
// to this onStatusChanged handler.
// engine.setOnStatusChanged(new EventHandler<WebEvent<java.lang.String>>() {
// @Override public void handle(WebEvent<String> t) {
// logger.log(Level.INFO, "Status Change: " + t.getData());
// }
// });
}
multigauge.setVal(Side.LEFT, useMiles ? ss.speed : Utils.milesToKm(ss.speed));
multigauge.setVal(Side.RIGHT, ss.power);
}
public void showSimpleMap(double lat, double lng, String title, int zoom) {
SimpleTemplate template = new SimpleTemplate(getClass().getResourceAsStream(SimpleMapTemplate));
String map = template.fillIn(
"LAT", String.valueOf(lat), "LONG", String.valueOf(lng),
"TITLE", title, "ZOOM", String.valueOf(zoom));
try {
File tempFile = File.createTempFile("VTTrip", ".html");
FileUtils.write(tempFile, map);
app.showDocument(tempFile.toURI().toString());
} catch (IOException ex) {
logger.warning("Unable to create temp file");
// TO DO: Pop up a dialog!
}
}
private String getMapFromTemplate(String lat, String lng, String heading) {
SimpleTemplate template = new SimpleTemplate(getClass().getResourceAsStream(MapTemplateFileName));
return template.fillIn(
"DIRECTION", heading, "LAT", lat, "LONG", lng,
"GMAP_API_KEY",
prefs.useCustomGoogleAPIKey.get() ?
prefs.googleAPIKey.get() :
Prefs.GoogleMapsAPIKey
);
}
/*------------------------------------------------------------------------------
*
* Handle the anitmation associated with the "loading" screen
*
*----------------------------------------------------------------------------*/
private void stopBlip(Animation blip) {
EventHandler<ActionEvent> cleanup = blip.getOnFinished();
blip.stop();
cleanup.handle(null);
}
private Animation animateBlip() {
final Circle core = new Circle(572, 360, 5);
final Circle blip = new Circle(572, 360, 5);
final Circle outline = new Circle(572, 360, 5);
Duration blipTime = Duration.seconds(1.5);
Duration interBlipTime = Duration.seconds(0.5);
core.setFill(Color.BLUE);
blip.setFill(Color.LIGHTBLUE);
outline.setFill(Color.TRANSPARENT);
outline.setStroke(Color.DARKBLUE);
outline.setStrokeWidth(0.25);
root.getChildren().addAll(blip, core, outline);
FadeTransition fadeBlip = new FadeTransition(blipTime, blip);
fadeBlip.setFromValue(0.8); fadeBlip.setToValue(0.0);
ScaleTransition scaleBlip = new ScaleTransition(blipTime, blip);
scaleBlip.setFromX(1); scaleBlip.setToX(4);
scaleBlip.setFromY(1); scaleBlip.setToY(4);
FadeTransition fadeOutline = new FadeTransition(blipTime, outline);
fadeOutline.setFromValue(1.0); fadeOutline.setToValue(0.0);
ScaleTransition scaleOutline = new ScaleTransition(blipTime, outline);
scaleOutline.setFromX(1); scaleOutline.setToX(4);
scaleOutline.setFromY(1); scaleOutline.setToY(4);
SequentialTransition sequence = new SequentialTransition (
new ParallelTransition(fadeBlip, scaleBlip, scaleOutline, fadeOutline),
new PauseTransition(interBlipTime));
sequence.setCycleCount(Timeline.INDEFINITE);
sequence.setOnFinished(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent t) {
core.setVisible(false);
blip.setVisible(false);
outline.setVisible(false);
}
});
sequence.play();
return sequence;
}
}