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 }