1 /*
   2  * Copyright (c) 2008, 2015, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.javafx.experiments.scheduleapp.pages;
  33 
  34 import static com.javafx.experiments.scheduleapp.ConferenceScheduleApp.*;
  35 import com.javafx.experiments.scheduleapp.TouchClickedEventAvoider;
  36 import com.javafx.experiments.scheduleapp.Page;
  37 import com.javafx.experiments.scheduleapp.PlatformIntegration;
  38 import static com.javafx.experiments.scheduleapp.Theme.*;
  39 import com.javafx.experiments.scheduleapp.data.DataService;
  40 import com.javafx.experiments.scheduleapp.model.Tweet;
  41 import java.util.regex.Matcher;
  42 import java.util.regex.Pattern;
  43 import javafx.application.Platform;
  44 import javafx.beans.value.ChangeListener;
  45 import javafx.beans.value.ObservableValue;
  46 import javafx.collections.ObservableList;
  47 import javafx.event.Event;
  48 import javafx.event.EventHandler;
  49 import javafx.geometry.Insets;
  50 import javafx.geometry.Pos;
  51 import javafx.geometry.VPos;
  52 import javafx.scene.Node;
  53 import javafx.scene.control.Button;
  54 import javafx.scene.control.Hyperlink;
  55 import javafx.scene.control.ListCell;
  56 import javafx.scene.control.ListView;
  57 import javafx.scene.control.Skin;
  58 import javafx.scene.image.Image;
  59 import javafx.scene.image.ImageView;
  60 import javafx.scene.input.MouseEvent;
  61 import javafx.scene.layout.HBox;
  62 import javafx.scene.layout.Priority;
  63 import javafx.scene.layout.Region;
  64 import javafx.scene.layout.VBox;
  65 import javafx.scene.paint.Color;
  66 import javafx.scene.shape.Rectangle;
  67 import javafx.scene.text.Text;
  68 import javafx.util.Callback;
  69 
  70 public class SocialPage extends Page {
  71     private final HBox box = new HBox(12);
  72     private final TwitterList recentTweetsList = new TwitterList();
  73     private final ListPopulator recentListPopulator;
  74     private final TwitterList popularTweetsList = new TwitterList();
  75     private final ListPopulator popularListPopulator;;
  76     private final Button refreshButton1;
  77     private final Button refreshButton2;
  78     private final Region spacer1;
  79     private final Region spacer2;
  80     private Button tweetButton;
  81 
  82     public SocialPage(DataService dataService) {
  83         super("Social", null);
  84         
  85         recentListPopulator = new ListPopulator(recentTweetsList, 
  86                 "http://search.twitter.com/search.json?q=%23"+dataService.getTwitterSearch()+"&rpp=100&result_type=recent");
  87         popularListPopulator = new ListPopulator(popularTweetsList, 
  88                 IS_TESTING_MODE ? 
  89                 "http://search.twitter.com/search.json?q="+dataService.getTwitterSearch()+"&rpp=100&geocode="+dataService.getTwitterLocalLatLon()+",1000mi" :
  90                 "http://search.twitter.com/search.json?q="+dataService.getTwitterSearch()+"&rpp=100&geocode="+dataService.getTwitterLocalLatLon()+",1mi");
  91         
  92         if (IS_BEAGLE) {
  93             new TouchClickedEventAvoider(recentTweetsList);
  94             new TouchClickedEventAvoider(popularTweetsList);
  95         }
  96         getChildren().setAll(box);
  97         box.setPadding(new Insets(12));
  98         
  99         box.setAlignment(Pos.CENTER);
 100 
 101         VBox recentBox = new VBox(12);
 102         HBox hb = new HBox(12);
 103         hb.setAlignment(Pos.CENTER);
 104         Text t1 = new Text(dataService.getTwitterSearch());
 105         t1.setFont(HUGE_FONT);
 106         t1.setFill(BLUE);
 107         Text t2 = new Text("Recent");
 108         t2.setFont(HUGE_FONT);
 109         t2.setFill(VLIGHT_GRAY);
 110         spacer1 = new Region();
 111         refreshButton1 = new Button();
 112         refreshButton1.setId("refresh-button");
 113         refreshButton1.setPrefWidth(162);
 114         refreshButton1.setPrefHeight(48);
 115         refreshButton1.setminWidth(162);
 116         refreshButton1.setMinHeight(48);
 117         refreshButton1.setOnMouseClicked(recentListPopulator);
 118 
 119         hb.getChildren().addAll(t1, t2, spacer1, refreshButton1);
 120         recentBox.getChildren().addAll(hb, recentTweetsList);
 121         VBox.setVgrow(recentTweetsList, Priority.ALWAYS);
 122         HBox hb = new HBox(12);
 123         hb.setAlignment(Pos.CENTER);
 124         Text t3 = new Text(dataService.getTwitterSearch());
 125         t3.setFont(HUGE_FONT);
 126         t3.setFill(PINK);
 127         Text t4 = new Text("Local");
 128         t4.setFont(HUGE_FONT);
 129         t4.setFill(VLIGHT_GRAY);
 130         spacer2 = new Region();
 131         refreshButton2 = new Button();
 132         refreshButton2.setId("refresh-button");
 133         refreshButton2.setPrefWidth(162);
 134         refreshButton2.setPrefHeight(48);
 135         refreshButton2.setMinWidth(162);
 136         refreshButton2.setMinHeight(48);
 137         refreshButton2.setOnMouseClicked(popularListPopulator);
 138         hb.getChildren().addAll(t3, t4, spacer2, refreshButton2);
 139         VBox localBox = new VBox(12, hb, popularTweetsList);
 140         VBox.setVgrow(popularTweetsList, Priority.ALWAYS);
 141 
 142         box.getChildren().addAll(recentBox, localBox);
 143         if (PlatformIntegration.supportsSendingTweets()) {
 144             tweetButton = new Button();
 145             tweetButton.setId("tweet-button");
 146             tweetButton.setPrefWidth(162);
 147             tweetButton.setPrefHeight(48);
 148             tweetButton.setMinHeight(48);
 149             tweetButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
 150                 @Override
 151                 public void handle(MouseEvent t) {
 152                     PlatformIntegration.tweet("#javaone ");
 153                 }
 154             });
 155             tweetButton.getStyleClass().clear();
 156             box.getChildren().add(tweetButton);
 157         }
 158         refreshButton1.getStyleClass().clear();
 159         refreshButton2.getStyleClass().clear();
 160         HBox.setHgrow(spacer1, Priority.ALWAYS);
 161         HBox.setHgrow(spacer2, Priority.ALWAYS);
 162         
 163         // refresh when switchimg to tab
 164         visibleProperty().addListener(new ChangeListener<Boolean>() {
 165             @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean newvalue) {
 166                 if (newvalue) {
 167                     recentListPopulator.handle(null);
 168                     popularListPopulator.handle(null);
 169                 }
 170             }
 171         });
 172     }
 173     
 174     @Override public void reset() {
 175         recentTweetsList.scrollTo(0);
 176         popularTweetsList.scrollTo(0);
 177     }
 178 
 179     @Override protected void layoutChildren() {
 180         final double w = getWidth();
 181         final double h = getHeight();
 182         box.resizeRelocate(0, 0, w, h);
 183     }
 184     
 185     private static class TwitterList extends ListView<Tweet> implements Callback<ListView<Tweet>, ListCell<Tweet>>{
 186         public TwitterList(){
 187             getStyleClass().setAll("twitter-list-view");
 188             skinClassNameProperty().set("com.sun.javafx.scene.control.skin.ListViewSkin");
 189             setCellFactory(this);
 190             setMinHeight(100);
 191         }
 192 
 193         @Override public ListCell<Tweet> call(ListView<Tweet> p) {
 194             return new TweetListCell();
 195         }
 196     }
 197     
 198     private static class ListPopulator implements EventHandler, Callback<Tweet[], Void>, Runnable {
 199         private final ListView<Tweet> list;
 200         private final String searchUrl;
 201         private volatile Tweet[] tweets;
 202 
 203         public ListPopulator(ListView<Tweet> list, String searchUrl) {
 204             this.list = list;
 205             this.searchUrl = searchUrl;
 206         }
 207         
 208 
 209         @Override public void handle(Event t) {
 210             PlatformIntegration.getTweetsForQuery(searchUrl, this);
 211         }
 212 
 213         @Override public Void call(Tweet[] tweets) {
 214             System.out.println("GOT TWEETS "+tweets.length);
 215             this.tweets = tweets;
 216             Platform.runLater(this);
 217             return null;
 218         }
 219 
 220         @Override public void run() {
 221             System.out.println("SET LIST ITEMS TO "+tweets.length);
 222             list.getItems().setAll(tweets);
 223             System.out.println("LIST ITEMS "+list.getItems().size());
 224         }
 225         
 226     }
 227 
 228     private static class TweetListCell extends ListCell<Tweet> implements Skin<TweetListCell>, EventHandler {
 229         private static final Pattern HTYPERLINK_PATTER = Pattern.compile("http(s)?://\\S+");
 230         private static final int PIC_SIZE = 48;
 231         private static final int GAP = 6;
 232         private static final int IMG_GAP = 12;
 233         private static final int MIN_HEIGHT = GAP + PIC_SIZE + 10 + GAP;
 234         private Text user = new Text();
 235         private Text message = new Text();
 236         private Text time = new Text();
 237         private ImageView image = new ImageView();
 238         private ImageView shadow = new ImageView(SHADOW_PIC);
 239         private Hyperlink hyperlink = null;
 240         private Rectangle imageBorder = new Rectangle(PIC_SIZE+6, PIC_SIZE+6,Color.WHITE);
 241         private Rectangle dividerLine = new Rectangle(1, 1);
 242         private int cellIndex;
 243         
 244         private TweetListCell() {
 245             super();
 246             //System.out.println("CREATED TimeSlot CELL " + (cellIndex));
 247             // we don't need any of the labeled functionality of the default cell skin, so we replace skin with our own
 248             // in this case using this same class as it saves memory. This skin is very simple its just a HBox container
 249             setSkin(this);
 250             getStyleClass().clear();
 251             user.setFont(BASE_FONT);
 252             user.setFill(DARK_GREY);
 253             user.setTextOrigin(VPos.TOP);
 254             message.setFont(LIGHT_FONT);
 255             message.setFill(DARK_GREY);
 256             message.setTextOrigin(VPos.TOP);
 257             time.setFont(BASE_FONT);
 258             time.setFill(BLUE);
 259             time.setTextOrigin(VPos.TOP);
 260             dividerLine.setFill(Color.web("#ddd"));
 261             dividerLine.setSmooth(false);
 262             shadow.setFitWidth(PIC_SIZE+6);
 263             getChildren().addAll(shadow, imageBorder, image,message,time,dividerLine);
 264             if (PlatformIntegration.supportsOpeningUrls()) {
 265                 hyperlink = new Hyperlink();
 266                 hyperlink.setOnMouseClicked(this);
 267                 getChildren().add(hyperlink);
 268             }
 269         }
 270 
 271         @Override public void resize(double width, double height) {
 272             super.resize(width, height);
 273             int textWith = (int)width - GAP - PIC_SIZE - GAP - GAP;
 274             message.setWrappingWidth(textWith);
 275         }
 276 
 277         @Override protected double computePrefWidth(double height) {
 278             return 100;
 279         }
 280 
 281         @Override protected double computePrefHeight(double width) {
 282 //            final int messageWidth = width 
 283             double calculatedHeight = GAP + user.getLayoutBounds().getHeight() + GAP + message.getLayoutBounds().getHeight() + GAP;
 284             if (hyperlink != null && hyperlink.getText() != null) {
 285                 calculatedHeight += 2 + hyperlink.prefHeight(-1);
 286             }
 287             return (int)((calculatedHeight<MIN_HEIGHT?MIN_HEIGHT:calculatedHeight)+ 0.5d);
 288         }
 289 
 290         @Override protected void layoutChildren() {
 291             final double w = getWidth();
 292             final double h = getHeight();
 293             final int textLeft = GAP+PIC_SIZE+IMG_GAP;
 294             dividerLine.setVisible(getIndex() != 0);
 295             dividerLine.setWidth(w);
 296             image.setLayoutX(GAP);
 297             image.setLayoutY(GAP+3);
 298             imageBorder.setLayoutX(GAP-3);
 299             imageBorder.setLayoutY(GAP);
 300             shadow.setLayoutX(GAP-3);
 301             shadow.setLayoutY(GAP+PIC_SIZE+6);
 302             user.setLayoutX(textLeft);
 303             user.setLayoutY(GAP);
 304             final int userHeight = (int)user.getLayoutBounds().getHeight();
 305             message.setLayoutX(textLeft);
 306             message.setLayoutY(GAP+userHeight+GAP);
 307             final int messageHeight = (int)message.getLayoutBounds().getHeight();
 308             final int timeWidth = (int)time.getLayoutBounds().getWidth();
 309             time.setLayoutX(w-GAP-timeWidth);
 310             time.setLayoutY(GAP);
 311             if (hyperlink != null) {
 312                 hyperlink.setLayoutX(textLeft);
 313                 hyperlink.setLayoutY(GAP+userHeight+GAP+messageHeight+2);
 314                 hyperlink.resize((int)(hyperlink.prefWidth(-1)+.5), (int)(hyperlink.prefHeight(-1)+.5));
 315             }
 316         }
 317         
 318 
 319         // CELL METHODS
 320         @Override protected void updateItem(Tweet tweet, boolean empty) {
 321             super.updateItem(tweet,empty);
 322             final ObservableList<Node> children = getChildren();
 323             if (tweet == null) { // empty item
 324                 for (Node child: children) child.setVisible(false);
 325             } else {
 326                 user.setText(tweet.getFromUserName());
 327                 user.setVisible(true);
 328                 String text = tweet.getText();
 329                 Matcher matcher = HTYPERLINK_PATTER.matcher(text);
 330                 String link = null;
 331                 if (matcher.find()) {
 332                     link = matcher.group();
 333                 }
 334                 text = text.replaceAll("http(s)?://\\S+", "");
 335                 message.setText(text);
 336                 message.setVisible(true);
 337                 time.setText("6m");
 338                 time.setVisible(true);
 339                 if (hyperlink != null) {
 340                     hyperlink.setText(link);
 341                     hyperlink.setVisible(true);
 342                 }
 343                 image.setVisible(true);
 344                 image.setImage(new Image(tweet.getProfileImageUrl(),PIC_SIZE,PIC_SIZE,true,true,true));
 345                 imageBorder.setVisible(true);
 346                 shadow.setVisible(true);
 347                 dividerLine.setVisible(true);
 348             }
 349         }
 350 
 351         // SKIN METHODS
 352         @Override public TweetListCell getSkinnable() { return this; }
 353         @Override public Node getNode() { return user; }
 354         @Override public void dispose() {}
 355 
 356         @Override public void handle(Event t) {
 357             PlatformIntegration.openUrl(hyperlink.getText());
 358         }
 359     }
 360     
 361 }