1 /* 2 * Copyright (c) 2008, 2013, 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 java.text.DateFormat; 35 import java.text.SimpleDateFormat; 36 import java.util.Calendar; 37 import java.util.TimeZone; 38 import javafx.beans.value.ChangeListener; 39 import javafx.beans.value.ObservableValue; 40 import javafx.collections.ObservableList; 41 import javafx.event.EventHandler; 42 import javafx.geometry.Bounds; 43 import javafx.geometry.Insets; 44 import javafx.geometry.Pos; 45 import javafx.geometry.VPos; 46 import javafx.scene.Group; 47 import javafx.scene.Node; 48 import javafx.scene.control.Button; 49 import javafx.scene.control.ListCell; 50 import javafx.scene.control.ListView; 51 import javafx.scene.control.Skin; 52 import javafx.scene.image.Image; 53 import javafx.scene.image.ImageView; 54 import javafx.scene.input.MouseEvent; 55 import javafx.scene.layout.Region; 56 import javafx.scene.layout.VBox; 57 import javafx.scene.paint.Color; 58 import javafx.scene.shape.Line; 59 import javafx.scene.shape.Polyline; 60 import javafx.scene.shape.Rectangle; 61 import javafx.scene.text.Font; 62 import javafx.scene.text.FontWeight; 63 import javafx.scene.text.Text; 64 import javafx.util.Callback; 65 import com.javafx.experiments.scheduleapp.TouchClickedEventAvoider; 66 import com.javafx.experiments.scheduleapp.ConferenceScheduleApp; 67 import com.javafx.experiments.scheduleapp.Page; 68 import com.javafx.experiments.scheduleapp.control.EventPopoverPage; 69 import com.javafx.experiments.scheduleapp.control.Popover; 70 import com.javafx.experiments.scheduleapp.data.DataService; 71 import com.javafx.experiments.scheduleapp.model.Event; 72 import com.javafx.experiments.scheduleapp.model.Session; 73 import com.javafx.experiments.scheduleapp.model.Track; 74 import com.sun.javafx.scene.control.skin.ListViewSkin; 75 76 import static com.javafx.experiments.scheduleapp.ConferenceScheduleApp.*; 77 import static com.javafx.experiments.scheduleapp.Theme.*; 78 79 public class TimelinePage extends Page implements Callback<ListView<Event>, ListCell<Event>> { 80 private static final DateFormat TIME_FORMAT = new SimpleDateFormat("hh:mma"); 81 private static final DateFormat DAY_FORMAT = new SimpleDateFormat("EEEE MMMM d"); 82 private static final Image DOT_IMAGE = new Image(ConferenceScheduleApp.class.getResource("images/timeline-dot.png").toExternalForm()); 83 private static final Image PRESENTATION_IMAGE = new Image(ConferenceScheduleApp.class.getResource("images/timeline-presentation.png").toExternalForm()); 84 private static final Image TOP_FADE_IMAGE = new Image(ConferenceScheduleApp.class.getResource("images/timeline-top-fade.png").toExternalForm()); 85 private static final Image BOTTOM_FADE_IMAGE = new Image(ConferenceScheduleApp.class.getResource("images/timeline-bottom-fade.png").toExternalForm()); 86 private static final Calendar TEMP_CALENDAR_1 = Calendar.getInstance(); 87 private static final Calendar TEMP_CALENDAR_2 = Calendar.getInstance(); 88 private static final int TOP = 20; 89 private static final int BOTTOM = 20; 90 private static final int LABEL_HEIGHT = 18; 91 private static final int BUBBLE_PADDING = 5; 92 private static final Font TIME_FONT = LARGE_FONT; 93 private static final Font TITLE_FONT = BASE_FONT; 94 private static final Color TITLE_COLOR = DARK_GREY; 95 private static final Font SPEAKERS_FONT = Font.font(DEFAULT_FONT_NAME, FontWeight.BOLD, 12); 96 private static final Color SPEAKERS_COLOR = Color.web("#5f5f5f"); 97 private static final Font SUMMARY_FONT = Font.font(DEFAULT_FONT_NAME, FontWeight.NORMAL, 11); 98 private static final Color SUMMARY_COLOR = DARK_GREY; 99 private static Image TOOTH_IMAGE = new Image(ConferenceScheduleApp.class.getResource("images/timeline-bubble-tooth.png").toExternalForm()); 100 101 static { 102 TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("PST")); 103 } 104 105 private ListView<Event> timelineListView; 106 private TimelineListViewSkin timelineListViewSkin; 107 private DayLabel pastLabel = new DayLabel(BLUE, null); 108 private DayLabel futureLabel = new DayLabel(BLUE, null); 109 private Rectangle line = new Rectangle(2,10,BLUE); 110 private Polyline topZigZag = new Polyline(0,0, 0,5, 10,10, -10,15, 0,20); 111 private Polyline bottomZigZag = new Polyline(0,0, 0,-5, -10,-10, 10,-15, 0,-20); 112 private ImageView topFade = new ImageView(TOP_FADE_IMAGE); 113 private ImageView bottomFade = new ImageView(BOTTOM_FADE_IMAGE); 114 private Button nowButton = new Button(); 115 private long currentTime = -1; 116 private final VBox notLoggedInContent = new VBox(20); 117 private Popover popover; 118 119 public TimelinePage(final Popover popover, final DataService dataService) { 120 super("My Timeline", dataService); 121 this.popover = popover; 122 timelineListView = new ListView<Event>() { 123 { 124 getStyleClass().clear(); 125 setId("timeline-list-view"); 126 setSkin(timelineListViewSkin = new TimelineListViewSkin(this)); 127 setCellFactory(TimelinePage.this); 128 setItems(dataService.getEvents()); 129 } 130 }; 131 if (IS_BEAGLE) { 132 new TouchClickedEventAvoider(timelineListView); 133 } 134 topZigZag.setStroke(BLUE); 135 topZigZag.setStrokeWidth(2); 136 bottomZigZag.setStroke(BLUE); 137 bottomZigZag.setStrokeWidth(2); 138 nowButton.setId("now-button"); 139 nowButton.getStyleClass().clear(); 140 nowButton.setOnMouseClicked(new EventHandler<MouseEvent>() { 141 @Override public void handle(MouseEvent t) { 142 // update current time 143 currentTime = dataService.getNow(); 144 // find first item starting after the current time 145 final ObservableList<Event> items = timelineListView.getItems(); 146 int itemToScrollTo = -1; 147 for(int i=0; i< items.size(); i++) { 148 if (items.get(i).getStart().getTime() > currentTime) { 149 itemToScrollTo = i; 150 break; 151 } 152 } 153 if (itemToScrollTo == -1) itemToScrollTo = items.size()-1; // time must be after all items 154 timelineListView.scrollTo(itemToScrollTo); 155 } 156 }); 157 158 ImageView loggedInMessage = new ImageView(new Image(ConferenceScheduleApp.class.getResource("images/need-to-be-logged-in.png").toExternalForm())); 159 Button loginBtn = new Button("Login"); 160 loginBtn.getStyleClass().setAll("large-light-blue-button"); 161 loginBtn.setOnMouseClicked(new EventHandler<MouseEvent>() { 162 @Override public void handle(MouseEvent event) { 163 ConferenceScheduleApp.getInstance().showLoginScreen(); 164 } 165 }); 166 notLoggedInContent.setAlignment(Pos.CENTER); 167 notLoggedInContent.getChildren().addAll(loggedInMessage, loginBtn); 168 169 getChildren().setAll(line,topZigZag,bottomZigZag,pastLabel,futureLabel,timelineListView, topFade, bottomFade, nowButton, notLoggedInContent); 170 171 ConferenceScheduleApp.getInstance().getSessionManagement().isGuestProperty().addListener(new ChangeListener<Boolean>() { 172 @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { 173 setIsGuest(newValue); 174 } 175 }); 176 setIsGuest(true); 177 } 178 179 @Override public void reset() { 180 timelineListView.scrollTo(0); 181 } 182 183 private void setIsGuest(boolean isGuest) { 184 if (isGuest) { 185 timelineListView.setVisible(false); 186 line.setVisible(false); 187 topZigZag.setVisible(false); 188 bottomZigZag.setVisible(false); 189 pastLabel.setVisible(false); 190 futureLabel.setVisible(false); 191 nowButton.setVisible(false); 192 topFade.setVisible(false); 193 bottomFade.setVisible(false); 194 notLoggedInContent.setVisible(true); 195 } else { 196 timelineListView.setVisible(true); 197 line.setVisible(true); 198 topZigZag.setVisible(true); 199 bottomZigZag.setVisible(true); 200 pastLabel.setVisible(true); 201 futureLabel.setVisible(true); 202 nowButton.setVisible(true); 203 topFade.setVisible(true); 204 bottomFade.setVisible(true); 205 notLoggedInContent.setVisible(false); 206 } 207 } 208 209 @Override protected void layoutChildren() { 210 final double w = getWidth(); 211 final int center = (int)(w/2d); 212 final double h = getHeight(); 213 pastLabel.setLayoutX(center); 214 pastLabel.setLayoutY(TOP); 215 futureLabel.setLayoutX(center); 216 futureLabel.setLayoutY(h - BOTTOM - LABEL_HEIGHT); 217 line.setLayoutX(center-1); 218 line.setLayoutY(TOP + LABEL_HEIGHT + 20); 219 line.setHeight(h - TOP - BOTTOM - LABEL_HEIGHT - LABEL_HEIGHT - 40); 220 line.setSmooth(false); 221 topZigZag.setLayoutX(center); 222 topZigZag.setLayoutY(TOP + LABEL_HEIGHT); 223 bottomZigZag.setLayoutX(center); 224 bottomZigZag.setLayoutY(h - BOTTOM - LABEL_HEIGHT); 225 timelineListView.resizeRelocate(0, TOP+LABEL_HEIGHT+20, w, h - TOP - BOTTOM - LABEL_HEIGHT - LABEL_HEIGHT - 40); 226 notLoggedInContent.resizeRelocate(0, TOP+LABEL_HEIGHT+20, w, h - TOP - BOTTOM - LABEL_HEIGHT - LABEL_HEIGHT - 40); 227 topFade.setLayoutY(TOP+LABEL_HEIGHT+20); 228 topFade.setFitWidth(w); 229 bottomFade.setLayoutY(h - BOTTOM - LABEL_HEIGHT - 34); 230 bottomFade.setFitWidth(w); 231 232 nowButton.resize(138,48); 233 nowButton.setLayoutX(w-5-138); 234 nowButton.setLayoutY(h-5-48); 235 } 236 237 @Override public ListCell<Event> call(ListView<Event> listView) { 238 return new TimelineListCell(); 239 } 240 241 private class TimelineListViewSkin extends ListViewSkin<Event> implements Runnable { 242 private TimelineListCell prevFirstCell = null, prevLastCell = null; 243 public TimelineListViewSkin(ListView<Event> listView) { 244 super(listView); 245 flow.setCellChangeNotificationListener(this); 246 } 247 248 @Override public void run() { 249 TimelineListCell firstCell = (TimelineListCell)flow.getFirstVisibleCell(); 250 if (firstCell != prevFirstCell && firstCell != null) { 251 Event firstVisibleEvent = firstCell.getItem(); 252 pastLabel.setText(DAY_FORMAT.format(firstVisibleEvent.getStart()).toUpperCase()); 253 } 254 TimelineListCell lastCell = (TimelineListCell)flow.getLastVisibleCell(); 255 if (lastCell != prevLastCell && lastCell != null) { 256 Event lastVisibleEvent = lastCell.getItem(); 257 if (lastVisibleEvent == null) lastVisibleEvent = timelineListView.getItems().get(timelineListView.getItems().size()-1); 258 futureLabel.setText(DAY_FORMAT.format(lastVisibleEvent.getStart()).toUpperCase()); 259 } 260 prevFirstCell = firstCell; 261 prevLastCell = lastCell; 262 } 263 } 264 265 /** 266 * Special Label node with a rounded rectangle background and a center-top origin. 267 */ 268 private class DayLabel extends Group { 269 private final Rectangle backgroundRect = new Rectangle(); 270 private final Text text = new Text(); 271 272 public DayLabel(Color color, String content) { 273 text.setTextOrigin(VPos.TOP); 274 text.setLayoutY(4); 275 text.setFont(TITLE_FONT); 276 text.setFill(Color.WHITE); 277 backgroundRect.setFill(color); 278 backgroundRect.setArcWidth(10); 279 backgroundRect.setArcHeight(10); 280 getChildren().addAll(backgroundRect, text); 281 if (content != null) setText(content); 282 } 283 284 public final void setText(String newText) { 285 text.setText(newText); 286 final Bounds textBounds = text.getBoundsInParent(); 287 final int textWidth = (int)(textBounds.getWidth()+0.5); 288 final int textHeight = (int)(textBounds.getHeight()+0.5); 289 text.setLayoutX(-textWidth/2); 290 backgroundRect.setLayoutX((-textWidth/2) - 5); 291 backgroundRect.setWidth(textWidth + 10); 292 backgroundRect.setHeight(textHeight + 6); 293 } 294 } 295 296 private class TimelineListCell extends ListCell<Event> implements Skin<TimelineListCell> { 297 private EventBubble bubble = new EventBubble(); 298 private Text timeText = new Text(); 299 private ImageView dot = new ImageView(DOT_IMAGE); 300 private ImageView icon = new ImageView(PRESENTATION_IMAGE); 301 private DayLabel dayLabel = new DayLabel(BLUE,null); 302 private DayLabel nowLabel = new DayLabel(PINK,"NOW"); 303 private Line nowLine = new Line(); 304 private boolean isOnRight = false; 305 private boolean isFirstOfDay = false; 306 private boolean isJustAfterNow = false; 307 private boolean isFirst = false; 308 private boolean isLast = false; 309 310 private TimelineListCell() { 311 super(); 312 // we don't need any of the labeled functionality of the default cell skin, so we replace skin with our own 313 // in this case using this same class as it saves memory. This skin is very simple its just a HBox container 314 setSkin(this); 315 getStyleClass().clear(); 316 timeText.setFont(TIME_FONT); 317 timeText.setFill(BLUE); 318 timeText.setTextOrigin(VPos.CENTER); 319 nowLine.setStroke(PINK); 320 nowLine.setStrokeWidth(2); 321 nowLine.getStrokeDashArray().setAll(5d,8d); 322 getChildren().addAll(timeText, dot, icon, dayLabel, nowLine, nowLabel); 323 } 324 325 @Override protected double computePrefWidth(double height) { 326 return 100; 327 } 328 329 @Override protected double computePrefHeight(double width) { 330 final Insets insets = getInsets(); 331 final double bubblePref = bubble.prefHeight((timelineListView.getWidth()/2)-30) + (isFirstOfDay?40:0) + (isJustAfterNow?40:0) + (isFirst||isLast?10:0); 332 return insets.getTop() + bubblePref + insets.getBottom(); 333 } 334 335 @Override protected void layoutChildren() { 336 final int top = (isFirstOfDay?40:0) + (isJustAfterNow?40:0) + (isFirst?10:0); 337 final double w = getWidth(); 338 final double h = getHeight() - top - (isLast?10:0); 339 final int centerX = (int)(w/2d); 340 final int centerY = (int)(h/2d) + top; 341 // final int dayLabelWidth = (int)dayLabel.prefWidth(-1); 342 // dayLabel.resizeRelocate(centerX - (int)(dayLabelWidth/2), 10, dayLabelWidth, LABEL_HEIGHT); 343 if (isFirstOfDay) { 344 dayLabel.setLayoutX(centerX); 345 dayLabel.setLayoutY(10); 346 } 347 if (isJustAfterNow) { 348 final int nowY = isFirstOfDay?50:10; 349 nowLabel.setLayoutX(centerX); 350 nowLabel.setLayoutY(nowY); 351 final int nowCenterY = 1 + nowY + (int)(nowLabel.getLayoutBounds().getHeight()/2d); 352 nowLine.setStartX(12); 353 nowLine.setStartY(nowCenterY); 354 nowLine.setEndX(w-18); 355 nowLine.setEndY(nowCenterY); 356 } 357 dot.relocate( 358 (int)(centerX - (dot.getLayoutBounds().getWidth()/2)), 359 (int)(centerY - (dot.getLayoutBounds().getHeight()/2))); 360 final Bounds iconBounds = icon.getLayoutBounds(); 361 if (isOnRight) { 362 bubble.resizeRelocate(centerX+20, top, w-centerX-30, h); 363 icon.relocate( 364 centerX - iconBounds.getWidth() - 12, 365 (int)(centerY - (iconBounds.getHeight()/2))); 366 timeText.relocate( 367 (int)(centerX - iconBounds.getWidth() - 10 -timeText.getBoundsInParent().getWidth()-10), 368 (int)(centerY-(timeText.getBoundsInParent().getHeight()/2))); 369 } else { 370 bubble.resizeRelocate(10, top, w-centerX-30, h); 371 icon.relocate( 372 centerX + 12, 373 (int)(centerY - (iconBounds.getHeight()/2))); 374 timeText.relocate( 375 (int)(centerX + 10 + iconBounds.getWidth() + 10), 376 (int)(centerY-(timeText.getBoundsInParent().getHeight()/2))); 377 } 378 } 379 380 // CELL METHODS 381 @Override protected void updateItem(Event item, boolean empty) { 382 if (getItem() != item || empty) { 383 // let super do its work 384 super.updateItem(item, empty); 385 // calculate if we are on right or left 386 final int index = getIndex(); 387 isFirst = index == 0; 388 isLast = index == (timelineListView.getItems().size()-1); 389 isOnRight = (index % 2) == 0; 390 bubble.setIsOnRight(isOnRight); 391 // update text and bubble 392 // System.out.println("updating cell ["+cellIndex+"] to type "+(item==null?"null":item.rowType)+" from "+oldRowType); 393 if (empty) { // empty item 394 timeText.setText(null); 395 bubble.setVisible(false); 396 dayLabel.setVisible(false); 397 nowLabel.setVisible(false); 398 nowLine.setVisible(false); 399 dot.setVisible(false); 400 icon.setVisible(false); 401 isFirstOfDay = false; 402 isJustAfterNow = false; 403 } else { 404 final Event prevItem = (index <= 0) ? null : timelineListView.getItems().get(index-1); 405 // see if we are first of day 406 if(index <= 0) { 407 isFirstOfDay = false; 408 } else { 409 TEMP_CALENDAR_1.setTime(prevItem.getStart()); 410 TEMP_CALENDAR_2.setTime(item.getStart()); 411 isFirstOfDay = TEMP_CALENDAR_1.get(Calendar.DAY_OF_YEAR) < TEMP_CALENDAR_2.get(Calendar.DAY_OF_YEAR); 412 } 413 if (isFirstOfDay) { 414 dayLabel.setText(DAY_FORMAT.format(item.getStart()).toUpperCase()); 415 } 416 dayLabel.setVisible(isFirstOfDay); 417 418 // see if need to show now label 419 currentTime = dataService.getNow(); 420 421 if(index <= 0) { 422 isJustAfterNow = currentTime < item.getStart().getTime(); 423 } else { 424 isJustAfterNow = currentTime < item.getEnd().getTime() && currentTime > prevItem.getEnd().getTime(); 425 } 426 nowLabel.setVisible(isJustAfterNow); 427 nowLine.setVisible(isJustAfterNow); 428 429 timeText.setText(TIME_FORMAT.format(item.getStart())); 430 bubble.setVisible(true); 431 bubble.setEvent(item); 432 dot.setVisible(true); 433 icon.setVisible(true); 434 requestLayout(); 435 } 436 } 437 } 438 439 // SKIN METHODS 440 @Override public TimelineListCell getSkinnable() { return this; } 441 @Override public Node getNode() { return bubble; } 442 @Override public void dispose() {} 443 } 444 445 private class EventBubble extends Region { 446 private Text titleText = new Text(); 447 private Text speakersText = new Text(); 448 private Text summaryText = new Text(); 449 private ImageView tooth = new ImageView(TOOTH_IMAGE); 450 private Rectangle trackColor = new Rectangle(5,5,10, 10); 451 private boolean isOnRight = false; 452 private Event event; 453 454 public EventBubble() { 455 getStyleClass().add("timelinev2-bubble"); 456 getChildren().addAll(titleText,speakersText,summaryText, trackColor, tooth); 457 titleText.setFont(TITLE_FONT); 458 titleText.setFill(TITLE_COLOR); 459 titleText.setTextOrigin(VPos.TOP); 460 speakersText.setFont(SPEAKERS_FONT); 461 speakersText.setFill(SPEAKERS_COLOR); 462 speakersText.setTextOrigin(VPos.TOP); 463 summaryText.setFont(BASE_FONT); 464 summaryText.setFill(DARK_GREY); 465 summaryText.setTextOrigin(VPos.TOP); 466 setPickOnBounds(true); 467 setOnMouseClicked(new EventHandler<MouseEvent>() { 468 @Override public void handle(MouseEvent t) { 469 if (event != null) { 470 EventPopoverPage page = new EventPopoverPage(dataService, event, false); 471 popover.clearPages(); 472 popover.pushPage(page); 473 popover.show(); 474 } 475 } 476 }); 477 } 478 479 public void setIsOnRight(boolean isOnRight) { 480 this.isOnRight = isOnRight; 481 } 482 483 @Override protected double computePrefWidth(double height) { 484 return 100; 485 } 486 487 @Override protected double computePrefHeight(double width) { 488 int textWrapWidth = (int) width - 20 - BUBBLE_PADDING; 489 490 // TEMPORARY CODE - textWrapWidth should be the same number that is computed in layoutChildren() 491 // but for some reason, it oscillates back and forth by 1 pixel causing wrapping to occur. 492 // The fix is to bump it up by one pixel and avoid the work 493 textWrapWidth++; 494 495 titleText.setWrappingWidth(textWrapWidth); 496 speakersText.setWrappingWidth(textWrapWidth); 497 summaryText.setWrappingWidth(textWrapWidth); 498 return (int)(BUBBLE_PADDING + titleText.getBoundsInParent().getHeight() + 499 BUBBLE_PADDING + speakersText.getBoundsInParent().getHeight() + 500 BUBBLE_PADDING + summaryText.getBoundsInParent().getHeight() + 501 BUBBLE_PADDING + 0.5); 502 } 503 504 @Override protected void layoutChildren() { 505 final int w = (int)getWidth(); 506 final int h = (int)getHeight(); 507 // layout tooth 508 if (isOnRight) { 509 tooth.setScaleX(1); 510 tooth.relocate( 511 -tooth.getLayoutBounds().getWidth()+1, 512 (h - tooth.getLayoutBounds().getHeight())/2); 513 } else { 514 tooth.setScaleX(-1); 515 tooth.relocate( 516 w-1, 517 (h - tooth.getLayoutBounds().getHeight())/2); 518 } 519 // layout color bar 520 trackColor.setHeight(h-10); 521 // layout text 522 final int textWrapWidth = w - 20 - BUBBLE_PADDING; 523 titleText.setWrappingWidth(textWrapWidth); 524 speakersText.setWrappingWidth(textWrapWidth); 525 summaryText.setWrappingWidth(textWrapWidth); 526 titleText.relocate(20, BUBBLE_PADDING); 527 final int titleHeight = (int)(titleText.getBoundsInParent().getHeight()+0.5); 528 speakersText.relocate(20, BUBBLE_PADDING + titleHeight + BUBBLE_PADDING); 529 final int speakersHeight = (int)(speakersText.getBoundsInParent().getHeight()+0.5); 530 summaryText.relocate(20, BUBBLE_PADDING + titleHeight + BUBBLE_PADDING + speakersHeight + BUBBLE_PADDING); 531 } 532 533 public void setEvent(Event event) { 534 this.event = event; 535 Session session = event.getSession(); 536 if (session != null) { 537 titleText.setText(session.getAbbreviation()+" :: "+session.getTitle()); 538 speakersText.setText(session.getSpeakersDisplay()); 539 summaryText.setText(event.getSessionTime().getRoom().toString()); 540 Track track = session.getTrack(); 541 trackColor.setFill(track==null? Color.gray(0.9) : Color.web(track.getColor())); 542 speakersText.setVisible(true); 543 } else if (event != null) { 544 titleText.setText(event.getTitle()); 545 if (event.getAttendees() != null) { 546 String attendees = ""; 547 for (String attendee: event.getAttendees()) { 548 if (attendees.length() != 0) attendees += ", "; 549 attendees += attendee; 550 } 551 speakersText.setText(attendees); 552 speakersText.setVisible(true); 553 } else { 554 speakersText.setVisible(false); 555 } 556 summaryText.setText(event.getLocation()==null?"":event.getLocation()); 557 trackColor.setFill(Color.BLACK); 558 } else { 559 titleText.setText(""); 560 summaryText.setText(""); 561 } 562 requestLayout(); 563 } 564 } 565 }