/* * Main.fx * * Created on 2009/12/01, 23:27:21 */ package twitfaces; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.io.http.HttpHeader; import javafx.scene.Group; import javafx.scene.control.TextBox; import javafx.scene.control.Button; import javafx.scene.input.MouseEvent; import javafx.io.http.HttpRequest; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.scene.text.TextOrigin; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import java.io.InputStream; import java.util.Comparator; import tweeter.parser.StatusParser; import tweeter.model.Status; import tweeter.model.User; import javafx.animation.Timeline; import javafx.animation.KeyFrame; import javafx.lang.Builtins; import javafx.util.Sequences; import javafx.animation.Interpolator; import javafx.scene.text.Font; import javafx.scene.CustomNode; import javafx.scene.Node; import javafx.scene.input.KeyEvent; import javafx.scene.effect.DropShadow; import javafx.scene.text.TextAlignment; /** * @author arton */ def PICSIZE : Number = 48; def CUWIDTH : Number = 200; def CUHEIGHT : Number = 240; var statuses : Friend[] = []; var readTimer : Timeline = Timeline { repeatCount: Timeline.INDEFINITE keyFrames : [ KeyFrame { time : 60s canSkip : true action : function() { readTimer.stop(); createFriendsTimelineReader().start(); } } ] } function createFriendsTimelineReader() : HttpRequest { return HttpRequest { location: "http://www.twitter.com/statuses/friends_timeline.xml" headers: [ HttpHeader.basicAuth(user.text, password.text) ] onInput: function(is: InputStream) { try { StatusParser{ updateStatus: updateStatus }.parse(is); } finally { is.close(); } } onException: function(ex: java.lang.Exception) { onException("{ex.getMessage()}"); } }; } function onException(msg : String) { } function setupStatusView() { if (currentView != friends) { stage.width = PICSIZE * 15 + 10; stage.height = PICSIZE * 15; currentView = friends; } else { friends.content = friends.content[e | e instanceof FaceNode]; } } class Friend { var user : User; var twitt : String[]; var node : FaceNode; var x: Number; var y: Number; } class IdComparator extends Comparator { override function compare(o1 : Object, o2 : Object) : Integer { var ox : Friend = o1 as Friend; var oy : Friend = o2 as Friend; return Integer.valueOf(ox.user.id) - Integer.valueOf(oy.user.id); } override function equals(o) : Boolean { return this == o; } } def idcomparator = IdComparator{}; class StatSizeComparator extends Comparator { override function compare(o1 : Object, o2 : Object) : Integer { var ox : Friend = o1 as Friend; var oy : Friend = o2 as Friend; return sizeof ox.twitt - sizeof oy.twitt; } override function equals(o) : Boolean { return this == o; } } def statSizeComparator = StatSizeComparator{}; function updateStatus(sts: Status[]): Void { Builtins.println("hello"); setupStatusView(); var cf = Friend{}; for (g : Status in sts) { var f : Friend; cf.user = g.user; Builtins.println("userid={cf.user.id}, name={g.user.name}, {g.user.screenName}"); var i: Integer = Sequences.binarySearch(statuses, cf, idcomparator); Builtins.println("index={i}"); if (i < 0) { f = Friend {user: g.user, twitt: []}; insert f before statuses[-i - 1]; } else { Builtins.println("found user"); f = statuses[i]; } if (sizeof f.twitt > 20) { f.twitt = f.twitt[0..18]; } insert g.text before f.twitt[0]; if (f.node != null) { f.node.twitt = g.text; } } var disp : Friend[] = statuses[0..sizeof statuses - 1]; Sequences.sort(disp, statSizeComparator); var x: Number = 0; var y: Number = 0; for (f in disp) { var img = Image{url: f.user.profileImageURL, }; Builtins.println("{f.user.screenName}: x={x}, Y={y}"); f.x = x; f.y = y; if (f.node != null) { f.node.imageView.image = img; f.node.play(x, y); } insert ImageView{fitHeight: PICSIZE, fitWidth: PICSIZE, image: img, layoutX: x, layoutY: y onMouseEntered: function(e : MouseEvent) : Void { Builtins.println("enter:{e}"); if (f.node == null) { f.node = FaceNode {}; insert f.node into friends.content; f.node.imageView.image = img; f.node.screenName = f.user.screenName; f.node.url = f.user.url; f.node.play(f.x, f.y); } if (sizeof f.twitt > 0) { f.node.twitt = f.twitt[0] } f.node.toFront(); } onMouseExited: function(e : MouseEvent) : Void { Builtins.println("exit:{e}"); } } into friends.content; (friends.content[sizeof friends.content - 1] as Node).toBack(); x += PICSIZE; if (x >= stage.width - PICSIZE / 2) { y += PICSIZE; x = 0; } } readTimer.play(); } var user = TextBox { promptText: "User" columns: 12 selectOnFocus: true translateX: 80 translateY: 30 }; var password = TextBox { promptText: "Password" columns: 12 selectOnFocus: true translateX: 80 translateY: 60 onKeyTyped: function(e: KeyEvent): Void { if (e.char == "\n") { loginAction(); } } }; var errmsg = Text { font : Font { size: 16 } x: 10, y: 120 fill: Color.RED } var starText : Text = Text { x: bind password.translateX + 5 y: bind password.translateY + 9 textOrigin: TextOrigin.TOP clip: Rectangle { x: bind starText.x y: bind starText.y width: password.layoutBounds.width - 15 height: bind password.boundsInLocal.height } }; var starBGRect = Rectangle { x: bind password.translateX + 2 y: bind password.translateY + 2 width: bind password.width - 4 height: bind password.height - 4 fill: Color.WHITE }; function loginAction() : Void { var hr : HttpRequest = createFriendsTimelineReader(); hr.onError = function(is: InputStream) { errmsg.content = "read failed"; is.close(); }; hr.start(); } var login : Group = Group { content: [ Text { font : Font { size: 12 } translateX: 10, translateY: 40 content: "User" } user, Text { font : Font { size: 12 } translateX: 10, translateY: 70 content: "Password" } password, starBGRect, starText, Button { text: "Login" translateX: 100 translateY: 100 onMouseClicked: function(e : MouseEvent) : Void { loginAction(); } } errmsg ] } class FaceNode extends CustomNode { var width: Number = CUWIDTH; var height: Number = CUHEIGHT; var nodeX : Number; var nodeY : Number; var move: Timeline; function play(sx: Number, sy: Number) : Void { if (move != null) { move.stop(); } move = Timeline { repeatCount: Timeline.INDEFINITE autoReverse: true keyFrames : [ at (0s) { nodeX => sx; nodeY => sy } at (6s) { nodeX => halfXPos() tween Interpolator.LINEAR; nodeY => halfYPos() tween Interpolator.LINEAR; } ] }; move.play(); } function halfXPos(): Number { return stage.width / 2 - width / 2; } function halfYPos(): Number { return stage.height / 2 - height / 2; } var imageView : ImageView = ImageView{fitHeight: PICSIZE, fitWidth: PICSIZE, layoutX: 5, layoutY: 5} var screenName: String; var url: String; var twitt: String; override function create():Node { return Group { // onMouseClicked : function(e: MouseEvent) : Void { // delete this from (parent as Group).content; // } onMouseEntered: function(e : MouseEvent) : Void { toFront(); } content: [ Rectangle { x: 0, y: 0 width: bind width, height: bind height fill: Color.web("#C0DEED"); effect: DropShadow { offsetX: 3 offsetY: 3 color: Color.BLACK radius: 5 } } imageView, Text { font : Font { size: 14 } x: 70, y: 30 content: bind screenName } Text { font : Font { size: 12 } content: bind url x: 5, y: 70 } Text { font : Font { size: 15 } wrappingWidth: 160 textAlignment: TextAlignment.JUSTIFY x: 5, y: 100 fill: Color.web("#0084B4") content: bind twitt } ], translateX: bind nodeX translateY: bind nodeY //transforms: Transform.scale(2, 2) } } } var friends : Group = Group { content: [ ] } def stars = "************************************"; var star = bind password.rawText on replace { if(password.rawText.length() < stars.length()) { starText.content = "{stars.substring(0, password.rawText.length())}"; } }; var currentView = login; var stage = Stage { title: "Twit Faces" width: 350 height: 350 scene: Scene { content: bind currentView } }