package net.javainthebox.flickrviewer;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
var imagePane = ImagePane {
translateX: 120
}
var thumbnailPane = ThumbnailPane {
update: imagePane.update
}
var reader = AtomReadWorker {
url: "http://api.flickr.com/services/feeds/photos_public.gne?id=57085156@N00&lang=en-us&format=atom"
thumbnailPane: thumbnailPane
}
reader.execute();
Stage {
title: "Flickr Searcher"
scene: Scene {
width: 660
height: 660
fill: Color.BLACK
content: [
imagePane,
thumbnailPane
]
}
}
package net.javainthebox.flickrviewer;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class Thumbnail extends CustomNode {
// 選択された時にコールされるコールバック関数
public var action: function(thumbnail: Thumbnail): Void;
// イメージのタイトル
public var title: String;
// サムネイルイメージの URL
// URLが変更したら、イメージをロードする
public var thumbnailUrl: String on replace {
if (thumbnailUrl != null) {
thumbnail = Image {
url: thumbnailUrl
}
}
}
// サムネイルイメージ
var thumbnail: Image;
// イメージのURL
public var imageUrl: String;
public-read def width: Number = 110.0;
public-read def height: Number = 110.0;
// 選択状態を表すフラグ
public var selected: Boolean = false;
// クリックされたら選択状態とし、コールバック関数をコールする
override var pressed on replace {
if (not selected and pressed) {
selected = true;
action(this);
}
}
public override function create(): Node {
Group {
content: [
Rectangle {
// 角丸四角にする
arcHeight: 10
arcWidth: 10
x: 0
y: 0
width: 109
height: 109
// 選択状態ではグレー、非選択状態では黒
fill: bind if (selected) Color.web("#666666") else Color.web("#222222")
stroke: Color.web("#666666")
// ドロップシャドウを施すことにより浮きでるような効果を出す
effect: DropShadow {
offsetX: 1
offsetY: 1
radius: 1
color: Color.LIGHTGRAY
}
},
ImageView {
// イメージは中央に配置する
x: bind (width - thumbnail.width)/2
y: bind (height - thumbnail.height)/2
image: bind thumbnail
}
]
}
}
}
package net.javainthebox.flickrviewer;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
// 三角形 サムネイルのページ送りに使用する
class Triangle extends CustomNode {
var x: Integer;
var y: Integer;
var width: Integer;
var height: Integer;
var isUpward: Boolean;
var action: function(): Void;
override var onMouseClicked = function(event: MouseEvent) {
if (not disable) {
action();
}
}
override function create(): Node {
Group {
content: [
Rectangle {
x: x
y: y
width: width,
height: height
stroke: Color.web("#666666")
fill: bind if (not disable) {
if (pressed) {
Color.GRAY
} else if (hover) {
Color.web("#666666")
} else {
Color.BLACK;
}
} else {
Color.BLACK;
}
},
if (isUpward) {
Polygon {
points: [
x + width/2, y + height*.2,
x + width/2 + height*.3, y + height*.8,
x + width/2 - height*.3, y + height*.8
]
fill: bind if (disable) Color.GRAY else Color.WHITE
}
} else {
Polygon {
points: [
x + width/2, y + height*.8,
x + width/2 + height*.3, y + height*.2,
x + width/2 - height*.3, y + height*.2
]
fill: bind if (disable) Color.GRAY else Color.WHITE
}
}
]
}
}
}
// サムネイルを表示するペイン
public class ThumbnailPane extends CustomNode {
// サムネイルの選択状態が変更したらコールするコールバック関数
public-init var update: function(title: String, url: String);
// サムネイル
// サムネイルの個数に応じて、ページ送りのフラグをセットする
public var thumbnails: Thumbnail[] on replace {
if (calcIndex(page) < sizeof thumbnails) {
forwardRemainsFlag = true;
}
for (thumbnail in thumbnails) {
thumbnail.action = thumbnailSelected;
}
updateVisibleThumbnails();
}
// 現在表示しているサムネイル
var visibleThumbnails: Thumbnail[];
// 選択されているサムネイル
var selectedThumbnail: Thumbnail;
// サムネイルのページ数
var page: Integer = 0;
// ページ送りができるかどうかを示すフラグ
var forwardRemainsFlag = false;
var backwardRemainsFlag = false;
def numPerVertical = 5;
def thumbnailSize = 120;
public-read def width: Integer = 120;
public-read def height: Integer = 660;
def arrowHeight = 25;
// サムネイルの選択状態が変更したらコールされる関数
function thumbnailSelected(thumbnail: Thumbnail): Void {
if (selectedThumbnail != null) {
selectedThumbnail.selected = false;
}
selectedThumbnail = thumbnail;
update(selectedThumbnail.title, selectedThumbnail.imageUrl);
}
// ページ送りされた場合に表示を更新する関数
function updateVisibleThumbnails() {
visibleThumbnails = for (y in [0.. numPerVertical - 1]) {
var index = page * numPerVertical + y;
var thumbnail = thumbnails[index];
thumbnail.translateX = 5;
thumbnail.translateY = y * thumbnailSize + 35;
thumbnail;
}
}
function calcIndex(page: Integer): Integer {
(page + 1) * numPerVertical;
}
public override function create(): Node {
Group{
content: bind [
Rectangle {
x: 0 y:0
width: width height: height
fill: null
stroke: Color.web("#666666")
}
Triangle {
x: 0 y: 0
width: width height: arrowHeight
isUpward: true
disable: bind not backwardRemainsFlag
action: function(): Void {
page--;
if (page == 0) {
backwardRemainsFlag = false;
}
if (forwardRemainsFlag == false
and calcIndex(page) < sizeof thumbnails) {
forwardRemainsFlag = true;
}
updateVisibleThumbnails();
}
}
visibleThumbnails,
Triangle {
x: 0 y: height - arrowHeight
width: width height: arrowHeight
isUpward: false
disable: bind not forwardRemainsFlag
action: function(): Void {
page++;
if (calcIndex(page) >= sizeof thumbnails) {
forwardRemainsFlag = false;
}
if (backwardRemainsFlag == false) {
backwardRemainsFlag = true;
}
updateVisibleThumbnails();
}
}
]
}
}
}
package net.javainthebox.flickrviewer;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.effect.Reflection;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
// イメージを表示するペイン
public class ImagePane extends CustomNode {
public-read var width = 540;
public-read var height = 660;
// 選択されたサムネイルが変更された時にコールされる関数
public function update(title: String, url: String): Void {
// イメージ
var image = Image {
url: url
// バックグラウンドでイメージをロードする
backgroundLoading: true
}
imageView = ImageView {
translateX: bind (width - image.width) / 2
translateY: bind (height - image.height) / 2
// 下にイメージを反射させるエフェクト
effect: Reflection {
fraction: 0.4
}
image: image
}
// イメージのタイトル
label = Text {
translateX: 10
translateY: 30
font: Font {
size: 18
}
fill: Color.WHITE
content: title
}
}
var imageView: ImageView;
var label: Text;
public override function create(): Node {
Group {
content: bind [label, imageView]
}
}
}
package net.javainthebox.flickrviewer;
import javafx.data.pull.Event;
import javafx.data.pull.PullParser;
import javafx.data.xml.QName;
import javafx.io.http.HttpRequest;
import java.io.InputStream;
import java.lang.Exception;
import java.net.URLEncoder;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.SwingWorker;
public class AtomReadWorker extends SwingWorker {
public-init var url: String;
public-init var thumbnailPane: ThumbnailPane;
// サムネイルのURLを取り出すための正規表現
def pattern = java.util.regex.Pattern.compile("http://farm.*_m.jpg");
// 非同期に行われる処理
public override function doInBackground(): Object {
// HTTP通信を行う
var request = HttpRequest {
location: url
onInput: parseAtom;
onException: function(ex: Exception) {
println("exception: {ex.getMessage()}");
}
};
request.enqueue();
}
// Atomのパース
function parseAtom(stream: InputStream) {
var thumbnail: Thumbnail;
var inEntry: Boolean;
var parser = PullParser {
documentType: PullParser.XML;
input: stream
onEvent: function(event: Event) {
if (event.type == PullParser.START_ELEMENT) {
if (event.qname.name == "entry") {
// <entry>の開始
// 新たなThumbnailオブジェクトを生成する
inEntry = true;
thumbnail = Thumbnail {};
}
}
if (event.type == PullParser.END_ELEMENT) {
if (event.qname.name == "entry") {
// </entry>の場合
// 作成したThumbnailオブジェクトをイベントディスパッチスレッドで
// 取得できるように、publishする
inEntry = false;
this.publish(thumbnail as Object);
} else if (inEntry and event.qname.name == "title") {
// </title>の場合
// Thumbnailオブジェクトのtitleを設定
thumbnail.title = event.text;
} else if (inEntry and event.qname.name == "content") {
// </content>の場合
// 正規表現を用いて、サムネイルのURLを取り出し、
// thumbnailUrlに設定
var matcher = pattern.matcher(event.text);
if (matcher.find()) {
var url = matcher.group();
thumbnail.thumbnailUrl = url.replaceAll("_m", "_t");
// 大きいイメージのURLを作成
thumbnail.imageUrl = url.replaceAll("_m", "");
}
}
}
}
}
// パースの開始
parser.parse();
stream.close();
}
// イベントディスパッチスレッドで処理される関数
// publishされたオブジェクトが引数となる
override function process(thumbnails: List) {
for (thumbnail in thumbnails) {
insert thumbnail as Thumbnail into thumbnailPane.thumbnails;
}
}
}