package localsearch;
import java.io.InputStream;
import java.lang.Exception;
import javafx.io.http.HttpRequest;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextBox;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import localsearch.model.Category;
import localsearch.model.Rating;
import localsearch.model.Restaurant;
import localsearch.parser.JSONPullParser;
import localsearch.parser.XMLPullParser;
import localsearch.view.ImageButton;
def dataType = "xml";
def appid = FX.getArgument("yahoo_appid");
var stageX = 280.0;
var stageY = 140.0;
def stageWidth = 240;
def stageHeight = 320;
var inBrowser = "true".equals(FX.getArgument("isApplet") as String);
var index:Integer = 0;
public var restaurants: Restaurant[];
public function searchCoffeeShops(zipcode:String) {
delete restaurants;
resetRestaurantDetails();
if(not validateZipCode(zipcode)) {
alert("Error", "Zip Code {zipcode} is not valid. Enter valid zip code.");
return;
}
def query = "coffee";
def results = 5;
def location = "http:;
var resultsProcessor: function(is: InputStream);
if("xml".equalsIgnoreCase(dataType)) {
resultsProcessor = XMLPullParser.processResults;
} else {
resultsProcessor = JSONPullParser.processResults;
}
println("Loading {dataType} data from {location}...\"");
alert("Please wait...", "Searching for Coffee Shops near zip code: {zipcode}...");
try {
var request = RequestHandler {
location: location
method: HttpRequest.GET
processResults: resultsProcessor
}
request.enqueue();
} catch (e:Exception) {
println("WARNING: {e}");
alert("Error", "Could not search... Please try again later.");
}
}
function validateZipCode(zipcode:String): Boolean {
try {
if(zipcode.length() == 5) {
var zipCodeInt = java.lang.Integer.valueOf(zipcode).intValue();
return (zipCodeInt > 0);
} else if (zipcode.length() == 10) {
var dashIndex = zipcode.indexOf("-");
if(dashIndex != 5) return false;
var firstPart = zipcode.substring(0, dashIndex);
var zipCodeInt = java.lang.Integer.valueOf(firstPart).intValue();
if(zipCodeInt <= 0) { return false; }
var secondPart = zipcode.substring(0, dashIndex);
zipCodeInt = java.lang.Integer.valueOf(secondPart).intValue();
return (zipCodeInt > 0);
}
} catch (e:Exception) { }
return false;
}
var bgImage:ImageView = ImageView {
focusable: true
image: Image {
url: "{__DIR__}images/background.png"
}
onKeyPressed:function(e:KeyEvent) {
if(e.code == KeyCode.VK_LEFT) {
onBack();
} else if(e.code == KeyCode.VK_RIGHT) {
onNext();
} else if(e.code == KeyCode.VK_DOWN) {
zipCodeText.requestFocus();
}
}
onMouseClicked:function(e:MouseEvent) {
bgImage.requestFocus();
}
}
var backButton = ImageButton {
x: 3
y: 150
normalImage: Image { url: "{__DIR__}images/arrow_left_normal.png" }
overImage: Image { url: "{__DIR__}images/arrow_left_over.png" }
onMouseClicked: function(e) {
onBack();
}
}
function onBack() {
if((sizeof restaurants) == 0) { return; }
index--; if(index < 0) { index = ((sizeof restaurants) - 1); }
showRestaurantDetails(index, false);
}
var nextButton = ImageButton {
x: stageWidth - 19
y: 150
normalImage: Image { url: "{__DIR__}images/arrow_right_normal.png" }
overImage: Image { url: "{__DIR__}images/arrow_right_over.png" }
onMouseClicked: function(e) {
onNext();
}
}
function onNext() {
if((sizeof restaurants) == 0) { return; }
index++; if(index >= (sizeof restaurants)) { index = 0; }
showRestaurantDetails(index, true);
}
var closeButton = ImageButton {
x: stageWidth - 20
y: 8
normalImage: Image { url: "{__DIR__}images/x_normal.png" }
overImage: Image { url: "{__DIR__}images/x_over.png" }
visible: bind (not inBrowser)
onMouseClicked: function(e) {
javafx.lang.FX.exit();
}
}
public function resetRestaurantDetails() {
shopName = "";
address = "";
city = "";
phone = "";
star.visible = false;
comments = "";
title = "Nearest Coffee Shops";
}
public function showRestaurantDetails(index:Integer, scrollLeft:Boolean) {
if(index >= (sizeof restaurants)) { return ; }
var scrollXVal = 1;
if(scrollLeft) { scrollXVal = -1; }
shopDetailsX = 0;
var timeline:Timeline = Timeline {
repeatCount:1
autoReverse: false
rate: 1.0
keyFrames: [
KeyFrame {
time: 250ms
values: [ shopDetailsX => scrollXVal * stageWidth tween Interpolator.LINEAR ]
action: function() {
shopDetailsX = scrollXVal * -stageWidth;
var result = restaurants[index];
shopName = trimString(result.title, 25);
address = trimString(result.address, 30);
city = trimString("{result.city} {result.state}", 30);
phone = "{result.phone}";
setStars(result.rating.averageRating);
var lastReview = "{result.rating.lastReviewIntro}";
comments = trimString("{lastReview}", 240);
title = "Coffee Shops ({index + 1} of {sizeof restaurants})";
}
},
KeyFrame {
time: 250ms
values: [ shopDetailsX => scrollXVal * -stageWidth tween Interpolator.DISCRETE ]
},
KeyFrame {
time: 500ms
values: [ shopDetailsX => 0 tween Interpolator.LINEAR ]
}
]
};
timeline.playFromStart();
}
function trimString(string:String, length:Integer) : String {
if(string == null) return "";
if(string.length() > length) {
return "{string.substring(0, length).trim()}...";
} else {
return string;
}
}
var defaultStarImage = Image {
url: "{__DIR__}images/star0.png"
}
var star = ImageView {
x: 25
y: 111
image: defaultStarImage
visible: false
}
function setStars(starCount:String) {
var imageSuffix = "0";
try {
var starIntCount = java.lang.Float.valueOf(starCount).intValue();
imageSuffix = "{starIntCount}";
if(starIntCount > 5) {
imageSuffix = "5";
} else if(starCount.indexOf(".") > 0) {
imageSuffix = "{imageSuffix}.5";
}
} catch (e:java.lang.Exception) {
e.printStackTrace();
}
star.image = Image {
url: "{__DIR__}images/star{imageSuffix}.png"
placeholder: defaultStarImage
}
star.visible = true;
}
var titleBar = Rectangle {
width: stageWidth
height: 25
fill: Color.TRANSPARENT
visible: bind (not inBrowser)
onMouseDragged: function(e) {
stageX += e.dragX;
stageY += e.dragY;
}
}
var title = "Nearest Coffee Shops";
var titleText = Text {
x: 45
y: 18
font: Font { name:"sansserif", size: 14 }
fill: Color.BLACK
content: bind title
}
var divider = Line {
startX: 0 startY: 25
endX: stageWidth endY: 25
stroke: Color.rgb(138, 110, 72)
}
var shopName = "";
var shopNameText = Text {
x: 25
y: 50
font: Font { name:"sansserif", size: 13 }
fill: Color.BLACK
content: bind shopName
}
var address = "";
var addressText = Text {
x: 25
y: 68
font: Font { name:"sansserif", size: 12 }
fill: Color.BLACK
content: bind address
}
var city = "";
var cityText = Text {
x: 25
y: 86
font: Font { name:"sansserif", size: 12 }
fill: Color.BLACK
content: bind city
}
var phone = "";
var phoneText = Text {
x: 25
y: 104
font: Font { name:"sansserif", size: 12 }
fill: Color.BLACK
content: bind phone
}
var comments = "";
var commentsText = Text {
x: 25
y: 147
font: Font { name:"sansserif", size: 11 }
fill: Color.BLACK
content: bind comments
wrappingWidth: stageWidth - 60
};
var shopDetailsX: Number = 0;
var shopDetailsDisplay = Group {
content: bind [
shopNameText, addressText, cityText, phoneText, commentsText, star
]
translateX: bind shopDetailsX
}
var shopDetailsGroup = Group {
content: [ shopDetailsDisplay ]
clip: Rectangle {
x: 15
y: 32
width: stageWidth - 30
height: stageHeight - 64
}
}
var zipCodeLabel = Text {
x: 15
y: stageHeight - 40
font: Font { name:"sansserif", size: 12 }
fill: Color.BLACK
content: "zip code:"
}
var textDeltaBounds: Rectangle = Rectangle {
x: 2 y: 2 width: 14 height: 5
};
var zipCodeText: TextBox = TextBox {
blocksMouse: true
translateX: 75
translateY: stageHeight - 58
columns: 7
selectOnFocus: false
text: "95054"
clip: Rectangle {
x: bind textDeltaBounds.x
y: bind textDeltaBounds.y
width: bind (zipCodeText.width - textDeltaBounds.width)
height: bind (zipCodeText.height - textDeltaBounds.height)
}
action: function() {
searchCoffeeShops(zipCodeText.text.trim());
}
onKeyPressed:function(e:KeyEvent) {
if(e.code == KeyCode.VK_UP) {
bgImage.requestFocus();
}
}
}
var zipCodeTextBorder = Rectangle {
x: bind zipCodeText.translateX + textDeltaBounds.x
y: bind zipCodeText.translateY + textDeltaBounds.y
width: bind (zipCodeText.width - textDeltaBounds.width)
height: bind (zipCodeText.height - textDeltaBounds.height)
fill: Color.TRANSPARENT
stroke: Color.rgb(138, 110, 72)
strokeWidth: 2.0
}
var searchButton = ImageButton {
x: stageWidth - 79
y: stageHeight - 57
normalImage: Image { url: "{__DIR__}images/search_normal.png" };
overImage: Image { url: "{__DIR__}images/search_over.png" };
onMouseClicked: function(e) {
searchCoffeeShops(zipCodeText.text.trim());
}
}
var serviceProviderText = Text {
x: 50
y: stageHeight - 12
font: Font { name:"sansserif", size: 11 }
fill: Color.rgb(96, 78, 51)
content: "Web Services by Yahoo!"
}
def stage = Stage {
title: "Coffee Shop Search"
resizable: false
x: bind stageX with inverse
y: bind stageY with inverse
width: stageWidth
height: stageHeight
visible: true
style: StageStyle.TRANSPARENT
scene: Scene {
content: Group {
content: bind [
bgImage, titleBar, titleText, divider, shopDetailsGroup, backButton, nextButton, closeButton,
zipCodeLabel, zipCodeText, zipCodeTextBorder, searchButton, serviceProviderText
]
clip: Rectangle {
width: stageWidth
height: stageHeight
arcWidth: 20
arcHeight: 20
}
}
fill: Color.TRANSPARENT
}
}
public function alert(alertTitle:String, msg:String): Void {
println(msg);
phone = alertTitle;
comments = trimString(msg, 240);
}
function run() {
if(FX.getProperty("javafx.me.profiles") != null) {
textDeltaBounds = Rectangle { x: 0 y: 0 width: 0 height: 0 }
}
searchCoffeeShops("95054");
}