1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package hello;
  27 
  28 
  29 import java.text.NumberFormat;
  30 import java.util.AbstractList;
  31 import java.util.BitSet;
  32 import java.util.Collection;
  33 import java.util.Comparator;
  34 import java.util.Date;
  35 import java.util.HashMap;
  36 import java.util.Map;
  37 import javafx.animation.Interpolator;
  38 import javafx.animation.KeyFrame;
  39 import javafx.animation.KeyValue;
  40 import javafx.animation.Timeline;
  41 
  42 import javafx.application.Application;
  43 import javafx.beans.InvalidationListener;
  44 import javafx.beans.Observable;
  45 import javafx.beans.value.ChangeListener;
  46 import javafx.beans.value.ObservableValue;
  47 import javafx.collections.FXCollections;
  48 import javafx.collections.ListChangeListener;
  49 import javafx.collections.ObservableList;
  50 import javafx.collections.transformation.SortedList;
  51 import javafx.collections.transformation.FilteredList;
  52 import javafx.event.ActionEvent;
  53 import javafx.event.EventHandler;
  54 import javafx.geometry.Insets;
  55 import javafx.geometry.Orientation;
  56 import javafx.scene.Group;
  57 import javafx.scene.Node;
  58 import javafx.scene.Scene;
  59 import javafx.scene.control.Button;
  60 import javafx.scene.control.ChoiceBox;
  61 import javafx.scene.control.Label;
  62 import javafx.scene.control.ListCell;
  63 import javafx.scene.control.ListView;
  64 import javafx.scene.control.ListView.EditEvent;
  65 import javafx.scene.control.RadioButton;
  66 import javafx.scene.control.SelectionMode;
  67 import javafx.scene.control.Tab;
  68 import javafx.scene.control.TabPane;
  69 import javafx.scene.control.TextField;
  70 import javafx.scene.control.ToggleGroup;
  71 import javafx.scene.control.Tooltip;
  72 import javafx.scene.control.cell.ChoiceBoxListCell;
  73 import javafx.scene.control.cell.TextFieldListCell;
  74 import javafx.scene.input.*;
  75 import javafx.scene.layout.GridPane;
  76 import javafx.scene.layout.Priority;
  77 import javafx.scene.layout.VBox;
  78 import javafx.scene.paint.Color;
  79 import javafx.scene.text.Font;
  80 import javafx.stage.Stage;
  81 import javafx.util.Callback;
  82 import javafx.util.Duration;
  83 
  84 public class HelloListView extends Application implements InvalidationListener {
  85 
  86     private static ObservableList<String> data = FXCollections.<String>observableArrayList();
  87     private static ObservableList<String> names = FXCollections.<String>observableArrayList();
  88     private static ObservableList<Number> money = FXCollections.<Number>observableArrayList();
  89     private static ObservableList<Map<String, String>> mapData = FXCollections.<Map<String, String>>observableArrayList();
  90 
  91     private static final String FIRST_NAME = "firstName";
  92     private static final String LAST_NAME = "lastName";
  93 
  94     static {
  95         data.addAll("Row 1", "Row 2",
  96 //                "Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Row 3",
  97                 "Row 4", "Row 5", "Row 6",
  98                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
  99                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 100 
 101                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 102                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 103                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 104 
 105                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 106                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 107                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 108 
 109                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 110                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 111                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 112 
 113                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 114                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 115                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 116 
 117                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 118                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 119                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 120 
 121                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 122                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 123                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "===== Row 20 ====",
 124 
 125                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 126                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 127                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 128 
 129                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 130                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 131                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20",
 132 
 133                 "Row 1", "Row 2", "Long Row 3", "Row 4", "Row 5", "Row 6",
 134                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 135                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", "Row 20"
 136         );
 137 
 138         names.addAll(
 139             // Top 100 boys names for 2010, taken from
 140             // http://www.babycenter.com/top-baby-names-2010
 141             "Aiden", "Jacob", "Jackson", "Ethan", "Jayden", "Noah", "Logan", "Caden",
 142 //            "Lucas", "Liam", "Mason", "Caleb", "Jack", "Brayden", "Connor", "Ryan",
 143 //            "Matthew", "Michael", "Alexander", "Landon,Nicholas", "Nathan", "Dylan",
 144 //            "Evan", "Benjamin", "Andrew", "Joshua", "Luke", "Gabriel", "William",
 145 //            "James", "Elijah", "Owen", "Tyler", "Gavin", "Carter", "Cameron", "Daniel",
 146 //            "Zachary", "Christian", "Joseph", "Wyatt", "Anthony", "Samuel", "Chase",
 147 //            "Max", "Isaac", "Christopher", "John", "Eli", "Austin", "Colton",
 148 //            "Hunter", "Tristan", "Jonathan", "David", "Alex", "Colin", "Dominic",
 149 //            "Cooper", "Henry", "Carson", "Isaiah", "Charlie", "Julian", "Grayson",
 150 //            "Cole", "Oliver", "Jordan", "Thomas", "Sean", "Brody", "Adam", "Levi",
 151 //            "Aaron", "Parker", "Sebastian", "Xavier", "Ian", "Miles", "Blake", "Jake",
 152 //            "Riley", "Jason", "Nathaniel", "Adrian", "Brandon", "Justin", "Nolan",
 153 //            "Jeremiah", "Hayden", "Devin", "Brady", "Robert", "Josiah", "Hudson",
 154 //            "Ryder", "Bryce", "Micah", "Sam",
 155 //
 156 //            // Top 100 girls names for 2010, taken from
 157 //            // http://www.babycenter.com/top-baby-names-2010
 158             "Sophia", "Isabella", "Olivia", "Emma", "Chloe", "Ava", "Lily", "Madison"
 159 //            "Addison", "Abigail", "Madelyn", "Emily", "Zoe", "Hailey", "Riley",
 160 //            "Ella", "Mia", "Kaitlyn", "Kaylee", "Peyton", "Layla,Avery", "Hannah",
 161 //            "Mackenzie", "Elizabeth", "Kylie", "Sarah", "Anna", "Grace", "Brooklyn",
 162 //            "Natalie", "Alyssa", "Alexis", "Aubrey", "Samantha", "Isabelle",
 163 //            "Arianna", "Charlotte,Makayla", "Claire", "Lillian", "Gabriella",
 164 //            "Lyla", "Amelia", "Sophie", "Aaliyah", "Taylor", "Audrey", "Bella",
 165 //            "Leah", "Allison", "Sydney", "Alana", "Maya", "Keira", "Lucy", "Kayla",
 166 //            "Lauren", "Savannah", "Brianna", "Ellie", "Reagan", "Evelyn", "Carly",
 167 //            "Julia", "Bailey", "Jordyn", "Victoria", "Annabelle", "Cadence",
 168 //            "Katherine", "Stella", "Molly", "Kennedy", "Jasmine,Gianna", "Abby",
 169 //            "Makenna", "Morgan", "Caroline", "Maria", "Brooke", "Nora", "Alexa",
 170 //            "Camryn", "Paige", "Eva", "Scarlett", "Adriana", "Juliana", "Ashlyn",
 171 //            "Megan", "Kendall", "Harper", "Jada", "Violet", "Alexandra", "Gracie",
 172 //            "Nevaeh", "Sadie"
 173         );
 174 
 175         money.addAll(43.68, 102.35, -23.67, 110.23, -43.93, 87.21);
 176 
 177         Map<String, String> map1 = new HashMap<String, String>();
 178         map1.put(FIRST_NAME, "Jonathan");
 179         map1.put(LAST_NAME, "Giles");
 180         Map<String, String> map2 = new HashMap<String, String>();
 181         map2.put(FIRST_NAME, "Brian");
 182         map2.put(LAST_NAME, "Beck");
 183         Map<String, String> map3 = new HashMap<String, String>();
 184         map3.put(FIRST_NAME, "Richard");
 185         map3.put(LAST_NAME, "Bair");
 186         Map<String, String> map4 = new HashMap<String, String>();
 187         map4.put(FIRST_NAME, "Jasper");
 188         map4.put(LAST_NAME, "Potts");
 189         Map<String, String> map5 = new HashMap<String, String>();
 190         map5.put(FIRST_NAME, "Will");
 191         map5.put(LAST_NAME, "Walker");
 192         mapData.addAll(map1, map2, map3, map4, map5);
 193     }
 194 
 195     /**
 196      * @param args the command line arguments
 197      */
 198     public static void main(String[] args) {
 199         Application.launch(args);
 200     }
 201 
 202     @Override public void start(Stage stage) {
 203         stage.setTitle("Hello ListView");
 204         final Scene scene = new Scene(new Group(), 875, 700);
 205         scene.setFill(Color.LIGHTGRAY);
 206         Group root = (Group)scene.getRoot();
 207 
 208 
 209         // TabPane
 210         final TabPane tabPane = new TabPane();
 211         tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
 212         tabPane.setPrefWidth(scene.getWidth());
 213         tabPane.setPrefHeight(scene.getHeight());
 214 
 215         InvalidationListener sceneListener = new InvalidationListener() {
 216             @Override public void invalidated(Observable ov) {
 217                 tabPane.setPrefWidth(scene.getWidth());
 218                 tabPane.setPrefHeight(scene.getHeight());
 219             }
 220         };
 221         scene.widthProperty().addListener(sceneListener);
 222         scene.heightProperty().addListener(sceneListener);
 223 
 224         // simple list view example
 225         Tab simpleTab = new Tab("Simple");
 226         buildSimpleTab(simpleTab);
 227         tabPane.getTabs().add(simpleTab);
 228 
 229         // horizontal list view example
 230         Tab horizontalTab = new Tab("Horizontal");
 231         buildHorizontalTab(horizontalTab);
 232         tabPane.getTabs().add(horizontalTab);
 233 
 234         // Cell Factory Tab
 235         Tab cellFactoriesTab = new Tab("Cell Factories");
 236         buildCellFactoriesTab(cellFactoriesTab);
 237         tabPane.getTabs().add(cellFactoriesTab);
 238 
 239         // Cell Editing Tab
 240         Tab cellEditingTab = new Tab("Cell Editing");
 241         buildCellEditingTab(cellEditingTab);
 242         tabPane.getTabs().add(cellEditingTab);
 243 
 244         // Cell Editing Tab
 245         Tab disappearingNodesTab = new Tab("RT-12822");
 246         buildDisappearingNodesTab(disappearingNodesTab);
 247         tabPane.getTabs().add(disappearingNodesTab);
 248 
 249         // sorted and filtered list view example
 250         Tab sortAndFilterTab = new Tab("Sort & Filter");
 251         buildSortAndFilterTab(sortAndFilterTab);
 252         tabPane.getTabs().add(sortAndFilterTab);
 253 
 254         // big list view example
 255         Tab bigListTab = new Tab("Big List");
 256         buildBigListTab(bigListTab);
 257         tabPane.getTabs().add(bigListTab);
 258 
 259         // big DnD example
 260         Tab dndTab = new Tab("DnD");
 261         buildDndTab(dndTab);
 262         tabPane.getTabs().add(dndTab);
 263 
 264         root.getChildren().add(tabPane);
 265 
 266         stage.setScene(scene);
 267         stage.show();
 268     }
 269 
 270     public void invalidated(Observable observable) {
 271         System.out.println("Event: " + observable);
 272     }
 273 
 274     private void buildSimpleTab(Tab tab) {
 275         GridPane grid = new GridPane();
 276 //        grid.setGridLinesVisible(true);
 277         grid.setPadding(new Insets(5, 5, 5, 5));
 278         grid.setHgap(5);
 279         grid.setVgap(5);
 280 
 281         // simple list view
 282         final ListView<String> listView = new ListView<String>();
 283         listView.setItems(data);
 284         listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
 285 
 286         listView.setOnEditStart(new EventHandler<EditEvent<String>>() {
 287             @Override public void handle(EditEvent<String> t) {
 288                 System.out.println("Edit Start: " + t.getIndex());
 289             }
 290         });
 291         listView.setOnEditCancel(new EventHandler<EditEvent<String>>() {
 292             @Override public void handle(EditEvent<String> t) {
 293                 System.out.println("Edit Cancel: " + t.getIndex());
 294             }
 295         });
 296         listView.setOnEditCommit(new EventHandler<EditEvent<String>>() {
 297             @Override public void handle(EditEvent<String> t) {
 298                 System.out.println("Edit Commit: " + t.getIndex());
 299             }
 300         });
 301 
 302         grid.add(listView, 0, 0, 1, 10);
 303         GridPane.setVgrow(listView, Priority.ALWAYS);
 304         GridPane.setHgrow(listView, Priority.ALWAYS);
 305         // --- simple listview
 306 
 307 
 308         // control buttons
 309         Button row5btn = new Button("Select 'Row 5'");
 310         row5btn.setOnAction(new EventHandler<ActionEvent>() {
 311             @Override public void handle(ActionEvent e) {
 312                 listView.getSelectionModel().clearSelection();
 313                 listView.getSelectionModel().select("Row 5");
 314             }
 315         });
 316         grid.add(row5btn, 1, 0);
 317 
 318         Button deselectRow5btn = new Button("Deselect item in 5th row");
 319         deselectRow5btn.setOnAction(new EventHandler<ActionEvent>() {
 320             @Override public void handle(ActionEvent e) {
 321                 listView.getSelectionModel().clearSelection(4);
 322             }
 323         });
 324         grid.getChildren().add(deselectRow5btn);
 325         GridPane.setConstraints(deselectRow5btn, 1, 1);
 326 
 327 
 328         Button row20focusBtn = new Button("Focus on item in 20th row");
 329         row20focusBtn.setOnAction(new EventHandler<ActionEvent>() {
 330             @Override public void handle(ActionEvent e) {
 331                 listView.getFocusModel().focus(19);
 332             }
 333         });
 334         grid.getChildren().add(row20focusBtn);
 335         GridPane.setConstraints(row20focusBtn, 1, 2);
 336 
 337         Button insertBeforeRow1btn = new Button("Add row before 0th row");
 338         insertBeforeRow1btn.setOnAction(new EventHandler<ActionEvent>() {
 339             @Override public void handle(ActionEvent e) {
 340                 data.add(0, new Date().toString());
 341             }
 342         });
 343         grid.getChildren().add(insertBeforeRow1btn);
 344         GridPane.setConstraints(insertBeforeRow1btn, 1, 3);
 345 
 346         Button insertBeforeRow5btn = new Button("Add row before 5th row");
 347         insertBeforeRow5btn.setOnAction(new EventHandler<ActionEvent>() {
 348             @Override public void handle(ActionEvent e) {
 349                 data.add(5, new Date().toString());
 350             }
 351         });
 352         grid.getChildren().add(insertBeforeRow5btn);
 353         GridPane.setConstraints(insertBeforeRow5btn, 1, 4);
 354 
 355         Button delete0thRow = new Button("Delete 0th row");
 356         delete0thRow.setOnAction(new EventHandler<ActionEvent>() {
 357             @Override public void handle(ActionEvent e) {
 358                 data.remove(0);
 359             }
 360         });
 361         grid.getChildren().add(delete0thRow);
 362         GridPane.setConstraints(delete0thRow, 1, 5);
 363 
 364         Button delete5thRow = new Button("Delete 5th row");
 365         delete5thRow.setOnAction(new EventHandler<ActionEvent>() {
 366             @Override public void handle(ActionEvent e) {
 367                 data.remove(4);
 368             }
 369         });
 370         grid.getChildren().add(delete5thRow);
 371         GridPane.setConstraints(delete5thRow, 1, 6);
 372 
 373         Button moveToRow40btn = new Button("Move to row 40");
 374         moveToRow40btn.setOnAction(new EventHandler<ActionEvent>() {
 375             @Override public void handle(ActionEvent e) {
 376                 listView.scrollTo(39);
 377             }
 378         });
 379         grid.getChildren().add(moveToRow40btn);
 380         GridPane.setConstraints(moveToRow40btn, 1, 8);
 381 
 382         tab.setContent(grid);
 383 
 384         //
 385         // observation code for debugging the simple list view multiple selection
 386         listView.getSelectionModel().selectedIndexProperty().addListener(new InvalidationListener() {
 387             public void invalidated(Observable ov) {
 388                 System.out.println("SelectedIndex: " + listView.getSelectionModel().getSelectedIndex());
 389             }
 390         });
 391 ////        listView.getSelectionModel().selectedItemProperty().addListener(new InvalidationListener() {
 392 ////            public void invalidated(ObservableValue ov) {
 393 ////                System.out.println("\tSelectedItem: " + listView.getSelectionModel().getSelectedItem());
 394 ////            }
 395 ////        });
 396 ////        listView.getFocusModel().focusedIndexProperty().addListener(new InvalidationListener() {
 397 ////            public void invalidated(ObservableValue ov) {
 398 ////                System.out.println("\tFocusedIndex: " + listView.getFocusModel().getFocusedIndex());
 399 ////            }
 400 ////        });
 401 ////        listView.getFocusModel().focusedItemProperty().addListener(new InvalidationListener() {
 402 ////            public void invalidated(ObservableValue ov) {
 403 ////                System.out.println("\tFocusedItem: " + listView.getFocusModel().getFocusedItem());
 404 ////            }
 405 ////        });
 406 //////        listView.getFocusModel().addInvalidationListener(FocusModel.FOCUSED_ITEM, this);
 407 ////
 408         listView.getSelectionModel().getSelectedIndices().addListener(new ListChangeListener<Integer>() {
 409             public void onChanged(Change<? extends Integer> change) {
 410                 while (change.next()) {
 411                     System.out.println("SelectedIndices: " + change.getList() +
 412                             ", removed: " + change.getRemoved() +
 413                             ", addedFrom: " + change.getFrom() +
 414                             ", addedTo: " + change.getTo());
 415                 }
 416             }
 417         });
 418 ////        ((MultipleSelectionModel)listView.getSelectionModel()).getSelectedItems().addListener(new ListChangeListener<String>() {
 419 ////            public void onChanged(Change<? extends String> c) {
 420 ////                System.out.println("SelectedIndices: " + c.getList() +
 421 ////                        ", removed: " + c.getRemoved() +
 422 ////                        ", addedFrom: " + c.getFrom() +
 423 ////                        ", addedTo: " + c.getTo());
 424 ////            }
 425 ////        });
 426     }
 427 
 428     private void buildHorizontalTab(Tab tab) {
 429         GridPane grid = new GridPane();
 430         grid.setPadding(new Insets(5, 5, 5, 5));
 431         grid.setHgap(5);
 432         grid.setVgap(5);
 433 
 434         // simple list view with content
 435         ListView horizontalListView = new ListView();
 436         horizontalListView.setOrientation(Orientation.HORIZONTAL);
 437         horizontalListView.setItems(data);
 438 
 439         grid.add(horizontalListView, 0, 0);
 440         GridPane.setVgrow(horizontalListView, Priority.ALWAYS);
 441         GridPane.setHgrow(horizontalListView, Priority.ALWAYS);
 442 
 443         // simple list view with content
 444         ListView emptyListView = new ListView();
 445         emptyListView.setOrientation(Orientation.HORIZONTAL);
 446 
 447         grid.add(emptyListView, 1, 0);
 448         GridPane.setVgrow(emptyListView, Priority.ALWAYS);
 449         GridPane.setHgrow(emptyListView, Priority.ALWAYS);
 450 
 451         tab.setContent(grid);
 452     }
 453 
 454     private void buildCellFactoriesTab(Tab tab) {
 455         GridPane grid = new GridPane();
 456         grid.setPadding(new Insets(5, 5, 5, 5));
 457         grid.setHgap(5);
 458         grid.setVgap(5);
 459 
 460         // number format listview
 461         final ListView<Number> listView = new ListView<Number>(money);
 462         listView.setCellFactory(new Callback<ListView<Number>, ListCell<Number>>() {
 463             @Override public ListCell<Number> call(ListView<Number> list) {
 464                 return new MoneyFormatCell();
 465             }
 466         });
 467         grid.add(listView, 0, 0);
 468         GridPane.setVgrow(listView, Priority.ALWAYS);
 469         GridPane.setHgrow(listView, Priority.ALWAYS);
 470         // --- number format listview
 471 
 472 
 473         // expanding cells listview
 474         final ListView<String> listView2 = new ListView<String>(data);
 475         listView2.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
 476             @Override public ListCell<String> call(ListView<String> list) {
 477                 return new ExpandOnSelectionCell<String>();
 478             }
 479         });
 480         grid.add(listView2, 1, 0);
 481         GridPane.setVgrow(listView2, Priority.ALWAYS);
 482         GridPane.setHgrow(listView2, Priority.ALWAYS);
 483         // --- expanding cells listview
 484 
 485 
 486         tab.setContent(grid);
 487     }
 488 
 489     private void buildDisappearingNodesTab(Tab tab) {
 490         GridPane grid = new GridPane();
 491         grid.setPadding(new Insets(5, 5, 5, 5));
 492         grid.setHgap(5);
 493         grid.setVgap(5);
 494 
 495         // Create a ListView<Button>
 496         ListView<Button> listView = new ListView<Button>();
 497         listView.setPrefWidth(100.0F);
 498         listView.setPrefHeight(100.0F);
 499         Button button = new Button("Apples");
 500         Tooltip tooltip = new Tooltip();
 501         tooltip.setText("Tooltip Apples");
 502         button.setTooltip(tooltip);
 503         Button button2 = new Button("Oranges");
 504         Tooltip tooltip2 = new Tooltip();
 505         tooltip2.setText("Tooltip Oranges");
 506         button2.setTooltip(tooltip2);
 507         Button button3 = new Button("Peaches");
 508         Tooltip tooltip3 = new Tooltip();
 509         tooltip3.setText("Tooltip Peaches");
 510         button3.setTooltip(tooltip3);
 511         Button button4 = new Button("Plums");
 512         Tooltip tooltip4 = new Tooltip();
 513         tooltip4.setText("Tooltip Plums");
 514         button4.setTooltip(tooltip4);
 515         Button button5 = new Button("Apricots");
 516         Tooltip tooltip5 = new Tooltip();
 517         tooltip5.setText("Tooltip Apricots");
 518         button5.setTooltip(tooltip5);
 519         Button button6 = new Button("Lemons");
 520         Tooltip tooltip6 = new Tooltip();
 521         tooltip6.setText("Tooltip Lemons");
 522         button6.setTooltip(tooltip6);
 523         Button button7 = new Button("Grapefruit");
 524         Tooltip tooltip7 = new Tooltip();
 525         tooltip7.setText("Tooltip Grapefruit");
 526         button7.setTooltip(tooltip7);
 527         Button button8 = new Button("Cherries");
 528         Tooltip tooltip8 = new Tooltip();
 529         tooltip8.setText("Tooltip Cherries");
 530         button8.setTooltip(tooltip8);
 531         listView.setItems(javafx.collections.FXCollections.observableArrayList(
 532                 button, button2, button3, button4, button5, button6, button7, button8));
 533 
 534         grid.add(listView, 0, 0, 1, 10);
 535         tab.setContent(grid);
 536     }
 537 
 538     private void buildCellEditingTab(Tab tab) {
 539         GridPane grid = new GridPane();
 540         grid.setPadding(new Insets(5, 5, 5, 5));
 541         grid.setHgap(5);
 542         grid.setVgap(5);
 543 
 544         // simple textfield list view
 545         final ListView<String> textFieldListView = new ListView<String>();
 546         textFieldListView.setEditable(true);
 547         textFieldListView.setItems(data);
 548         textFieldListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
 549         textFieldListView.setCellFactory(TextFieldListCell.forListView());
 550         textFieldListView.setOnEditStart(new EventHandler<EditEvent<String>>() {
 551             @Override public void handle(EditEvent<String> t) {
 552                 System.out.println("On Edit Start: " + t);
 553             }
 554         });
 555         textFieldListView.setOnEditCancel(new EventHandler<EditEvent<String>>() {
 556             @Override public void handle(EditEvent<String> t) {
 557                 System.out.println("On Edit Cancel: " + t);
 558             }
 559         });
 560         grid.add(textFieldListView, 0, 0, 1, 10);
 561         GridPane.setVgrow(textFieldListView, Priority.ALWAYS);
 562         GridPane.setHgrow(textFieldListView, Priority.ALWAYS);
 563         // --- simple listview
 564 
 565         // simple choicebox list view
 566         final ObservableList<String> options = FXCollections.observableArrayList("Jenny", "Billy", "Timmy");
 567         final ListView<String> choiceBoxListView = new ListView<String>();
 568         choiceBoxListView.setEditable(true);
 569         choiceBoxListView.setItems(data);
 570         choiceBoxListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
 571         choiceBoxListView.setCellFactory(ChoiceBoxListCell.forListView(options));
 572         grid.add(choiceBoxListView, 1, 0, 1, 10);
 573         GridPane.setVgrow(choiceBoxListView, Priority.ALWAYS);
 574         GridPane.setHgrow(choiceBoxListView, Priority.ALWAYS);
 575         // --- simple listview
 576 
 577 
 578         // control buttons
 579         final Button editRow3btn = new Button("Edit row 3");
 580         editRow3btn.setOnAction(new EventHandler<ActionEvent>() {
 581             public void handle(ActionEvent t) {
 582                 textFieldListView.edit(2);
 583                 choiceBoxListView.edit(2);
 584             }
 585         });
 586         grid.getChildren().add(editRow3btn);
 587         GridPane.setConstraints(editRow3btn, 2, 0);
 588 
 589         final Button cancelEditBtn = new Button("Cancel edit");
 590         cancelEditBtn.setOnAction(new EventHandler<ActionEvent>() {
 591             public void handle(ActionEvent t) {
 592                 textFieldListView.edit(-1);
 593                 choiceBoxListView.edit(-1);
 594             }
 595         });
 596         grid.getChildren().add(cancelEditBtn);
 597         GridPane.setConstraints(cancelEditBtn, 2, 1);
 598 
 599         tab.setContent(grid);
 600     }
 601 
 602 //    private void buildCellsTab(Tab tab) {
 603 //        GridPane grid = new GridPane();
 604 //        grid.setPadding(new Insets(5, 5, 5, 5));
 605 //        grid.setHgap(5);
 606 //        grid.setVgap(5);
 607 //
 608 //        // add a complex listview (using pre-built cell factory)
 609 //        final ListView<Number> listView2 = new ListView<Number>();
 610 //        listView2.setItems(money);
 611 //        listView2.setCellFactory(MoneyFormatCellFactory.listView());
 612 //        grid.getChildren().add(listView2);
 613 //        GridPane.setVgrow(listView2, Priority.ALWAYS);
 614 //        GridPane.setConstraints(listView2, 0, 0);
 615 //        // --- complex listview
 616 //
 617 //        // add another complex listview (using pre-built cell factory)
 618 //        final ListView<Map<String, String>> listView3 = new ListView<Map<String, String>>();
 619 //        listView3.setItems(mapData);
 620 ////        listView3.setCellFactory(Cells.ListView.mapProperty(FIRST_NAME));
 621 //        listView3.setCellFactory(MapValueCellFactory.listView("First Name: %1$s\r\nLast Name: %2$s", FIRST_NAME, LAST_NAME));
 622 //        grid.getChildren().add(listView3);
 623 //        GridPane.setVgrow(listView3, Priority.ALWAYS);
 624 //        GridPane.setConstraints(listView3, 1, 0);
 625 //        // --- complex listview
 626 //
 627 //        tab.setContent(grid);
 628 //    }
 629 
 630 
 631     private Comparator<String> alphabeticalComparator = new Comparator<String>() {
 632         @Override public int compare(String o1, String o2) {
 633             return o1.compareTo(o2);
 634         }
 635     };
 636 
 637     private Comparator<String> reverseAlphabeticalComparator = new Comparator<String>() {
 638         @Override public int compare(String o1, String o2) {
 639             return o2.compareTo(o1);
 640         }
 641     };
 642 
 643     private void buildSortAndFilterTab(Tab tab) {
 644         // initially we match everything in the filter list
 645 
 646         final SortedList<String> sortedList = new SortedList<String>(names);
 647         final FilteredList<String> filteredList = new FilteredList<String>(sortedList, e -> true);
 648 
 649         GridPane grid = new GridPane();
 650         grid.setPadding(new Insets(5, 5, 5, 5));
 651         grid.setHgap(5);
 652         grid.setVgap(5);
 653 
 654         // --- unmodified listview
 655         final ListView<String> unmodifiedListView = new ListView<String>();
 656         unmodifiedListView.setId("Unmodified list");
 657         unmodifiedListView.setItems(names);
 658 //        unmodifiedListView.setCellFactory(TextFieldCellFactory.listView());
 659         Node unmodifiedLabel = createLabel("Original ListView:");
 660         grid.getChildren().addAll(unmodifiedLabel, unmodifiedListView);
 661         GridPane.setConstraints(unmodifiedLabel, 0, 0);
 662         GridPane.setConstraints(unmodifiedListView, 0, 1);
 663         GridPane.setVgrow(unmodifiedListView, Priority.ALWAYS);
 664         // --- unmodified listview
 665 
 666 
 667         // --- sorted listview
 668         final ListView<String> sortedListView = new ListView<String>();
 669         sortedListView.setId("sorted list");
 670         sortedListView.setItems(sortedList);
 671 //        sortedListView.setCellFactory(TextFieldCellFactory.listView());
 672         Node sortedLabel = createLabel("Sorted ListView:");
 673         grid.getChildren().addAll(sortedLabel, sortedListView);
 674         GridPane.setConstraints(sortedLabel, 1, 0);
 675         GridPane.setConstraints(sortedListView, 1, 1);
 676         GridPane.setVgrow(sortedListView, Priority.ALWAYS);
 677         // --- sorted listview
 678 
 679 
 680         // --- filtered listview
 681         final ListView<String> filteredListView = new ListView<String>();
 682         filteredListView.setId("filtered list");
 683         filteredListView.setItems(filteredList);
 684 //        filteredListView.setCellFactory(TextFieldCellFactory.listView());
 685         Node filteredLabel = createLabel("Filtered (and sorted) ListView:");
 686         grid.getChildren().addAll(filteredLabel, filteredListView);
 687         GridPane.setConstraints(filteredLabel, 2, 0);
 688         GridPane.setConstraints(filteredListView, 2, 1);
 689         GridPane.setVgrow(filteredListView, Priority.ALWAYS);
 690         // --- filtered listview
 691 
 692 
 693         // control buttons
 694         VBox vbox = new VBox(10);
 695 
 696         vbox.getChildren().add(new Label("Note: Double-click list cells to edit."));
 697 
 698         final TextField filterInput = new TextField();
 699         filterInput.setPromptText("Enter filter text");
 700 //        filterInput.setColumns(35);
 701         filterInput.setOnKeyReleased(new EventHandler<KeyEvent>() {
 702             public void handle(KeyEvent t) {
 703                 filteredList.setPredicate((String e) ->
 704                         e.toUpperCase().contains(filterInput.getText().toUpperCase()));
 705             }
 706         });
 707         vbox.getChildren().add(filterInput);
 708 
 709         final TextField newItemInput = new TextField();
 710         newItemInput.setPromptText("Enter text, then press enter to add item to list");
 711 //        newItemInput.setColumns(35);
 712         newItemInput.setOnKeyReleased(new EventHandler<KeyEvent>() {
 713             public void handle(KeyEvent t) {
 714                 if (t.getCode() == KeyCode.ENTER) {
 715                     names.add(newItemInput.getText());
 716                     newItemInput.setText("");
 717                 }
 718             }
 719         });
 720         vbox.getChildren().add(newItemInput);
 721 
 722         // sort ascending
 723         final ToggleGroup toggleGroup = new ToggleGroup();
 724         toggleGroup.selectedToggleProperty().addListener(new InvalidationListener() {
 725             public void invalidated(Observable ov) {
 726                 if (toggleGroup.getSelectedToggle() == null) return;
 727                 sortedList.setComparator((Comparator<String>)toggleGroup.getSelectedToggle().getUserData());
 728             }
 729         });
 730         final RadioButton sortAscBtn = new RadioButton("Sort Ascending");
 731         sortAscBtn.setUserData(alphabeticalComparator);
 732         sortAscBtn.setToggleGroup(toggleGroup);
 733         sortAscBtn.setSelected(true);
 734 
 735         final RadioButton sortDescBtn = new RadioButton("Sort Descending");
 736         sortDescBtn.setUserData(reverseAlphabeticalComparator);
 737         sortDescBtn.setToggleGroup(toggleGroup);
 738 
 739         vbox.getChildren().addAll(sortAscBtn, sortDescBtn);
 740 
 741         grid.setConstraints(vbox, 3, 1);
 742         grid.getChildren().add(vbox);
 743 
 744         tab.setContent(grid);
 745     }
 746 
 747     private void buildBigListTab(Tab tab) {
 748         GridPane grid = new GridPane();
 749         grid.setPadding(new Insets(5, 5, 5, 5));
 750         grid.setHgap(5);
 751         grid.setVgap(5);
 752 
 753         BigList intList = new BigList();
 754 
 755         // simple list view
 756         final ListView<Integer> listView = new ListView<Integer>();
 757         listView.setItems(intList);
 758 //        listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION);
 759         grid.add(listView, 0, 0);
 760         GridPane.setVgrow(listView, Priority.ALWAYS);
 761         GridPane.setHgrow(listView, Priority.ALWAYS);
 762 
 763         tab.setContent(grid);
 764     }
 765 
 766     private void buildDndTab(Tab tab) {
 767         GridPane grid = new GridPane();
 768         grid.setPadding(new Insets(5, 5, 5, 5));
 769         grid.setHgap(5);
 770         grid.setVgap(5);
 771 
 772         ObservableList<String> listOneItems = FXCollections.observableArrayList(names.subList(0, 8));
 773         ObservableList<String> listTwoItems = FXCollections.observableArrayList(names.subList(8, 16));
 774 
 775         Label introLabel = new Label("On Windows: DnD is a MOVE, hold ctrl/cmd whilst dragging for COPY.\n"
 776                                     +"On Mac: DnD is a COPY. With any modifier held, no DnD operation is performed.");
 777         introLabel.setWrapText(true);
 778         introLabel.setFont(Font.font(14));
 779         grid.add(introLabel, 0, 0, 2, 1);
 780 
 781         // --- list one
 782         final ListView<String> listOne = new ListView<String>();
 783         listOne.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
 784         listOne.setItems(listOneItems);
 785         Node listOneLabel = createLabel("List One:");
 786         grid.getChildren().addAll(listOneLabel, listOne);
 787         GridPane.setConstraints(listOneLabel, 0, 1);
 788         GridPane.setConstraints(listOne, 0, 2);
 789         GridPane.setVgrow(listOne, Priority.ALWAYS);
 790         // --- list one
 791 
 792         // --- list two
 793         final ListView<String> listTwo = new ListView<String>();
 794         listTwo.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
 795         listTwo.setItems(listTwoItems);
 796         Node listTwoLabel = createLabel("List Two:");
 797         grid.getChildren().addAll(listTwoLabel, listTwo);
 798         GridPane.setConstraints(listTwoLabel, 1, 1);
 799         GridPane.setConstraints(listTwo, 1, 2);
 800         GridPane.setVgrow(listTwo, Priority.ALWAYS);
 801         // --- list two
 802 
 803         // set up Dnd in both directions
 804         EventHandler<MouseEvent> dragDetected = new EventHandler<MouseEvent>() {
 805             public void handle(MouseEvent event) {
 806                 ListView<String> list = (ListView) event.getSource();
 807                 Dragboard db = list.startDragAndDrop(TransferMode.ANY);
 808 
 809                 ClipboardContent content = new ClipboardContent();
 810                 content.putString(list.getSelectionModel().getSelectedItem());
 811                 db.setContent(content);
 812 
 813                 event.consume();
 814             }
 815         };
 816         EventHandler<DragEvent> dragOver = new EventHandler<DragEvent>() {
 817             public void handle(DragEvent event) {
 818                 if (event.getGestureSource() != event.getTarget() && event.getDragboard().hasString()) {
 819                     event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
 820                 }
 821 
 822                 event.consume();
 823             }
 824         };
 825         EventHandler<DragEvent> dragDropped = new EventHandler<DragEvent>() {
 826             public void handle(DragEvent event) {
 827                 ListView<String> list = (ListView) event.getGestureTarget();
 828 
 829                 Dragboard db = event.getDragboard();
 830                 boolean success = false;
 831                 if (db.hasString()) {
 832                     list.getItems().add(db.getString());
 833                     success = true;
 834                 }
 835 
 836                 event.setDropCompleted(success);
 837                 event.consume();
 838             }
 839         };
 840         EventHandler<DragEvent> dragDone = new EventHandler<DragEvent>() {
 841             public void handle(DragEvent event) {
 842                 if (event.getTransferMode() == TransferMode.MOVE) {
 843                     ListView<String> list = (ListView) event.getGestureSource();
 844                     list.getItems().remove(event.getDragboard().getString());
 845                 }
 846                 event.consume();
 847             }
 848         };
 849 
 850         listOne.setOnDragDetected(dragDetected);
 851         listOne.setOnDragOver(dragOver);
 852         listOne.setOnDragDropped(dragDropped);
 853         listOne.setOnDragDone(dragDone);
 854 
 855         listTwo.setOnDragDetected(dragDetected);
 856         listTwo.setOnDragOver(dragOver);
 857         listTwo.setOnDragDropped(dragDropped);
 858         listTwo.setOnDragDone(dragDone);
 859 
 860         tab.setContent(grid);
 861     }
 862 
 863     private Node createLabel(String text) {
 864         Label label = new Label(text);
 865         return label;
 866     }
 867 
 868 //    public void handle(Bean bean, PropertyReference<?> pr) {
 869 //        SelectionModel sm = null;
 870 //        FocusModel fm = null;
 871 //        if (bean instanceof SelectionModel) {
 872 //            System.out.print("Selection Event: ");
 873 //            sm = (SelectionModel) bean;
 874 //        } else if (bean instanceof FocusModel) {
 875 //            System.out.print("Focus Event: ");
 876 //            fm = (FocusModel) bean;
 877 //        }
 878 //
 879 //
 880 //        if (pr == SelectionModel.SELECTED_INDEX) {
 881 //            System.out.println("\tSelectedIndex: " + sm.getSelectedIndex());
 882 //        } else if (pr == SelectionModel.SELECTED_ITEM) {
 883 //            System.out.println("\tSelectedItem: " + sm.getSelectedItem());
 884 //        } else if (pr == FocusModel.FOCUSED_INDEX) {
 885 //            System.out.println("\tFocusedIndex: " + fm.getFocusedIndex());
 886 //        } else if (pr == FocusModel.FOCUSED_ITEM) {
 887 //            System.out.println("\tFocusedItem: " + fm.getFocusedItem());
 888 //        }
 889 //    }
 890 
 891     private static class BigList extends AbstractList<Integer> implements ObservableList<Integer> {
 892 
 893         @Override
 894         public Integer get(int index) {
 895             return index;
 896         }
 897 
 898         @Override
 899         public int size() {
 900             // We have to be somewhat conscious of memory use, in relation to
 901             // the ListView selection model, which is currently implemented as a
 902             // BitSet. This means that if we have too many items in a list
 903             // (e.g Integer.MAX_VALUE) and we select an item towards the end of
 904             // the list, we will get an OOME as we require a bit for every item
 905             // up to the selected item (although they would all be 0/false, it
 906             // still costs memory). Therefore, we limit the size of the list
 907             // to prevent this from being a concern.
 908             // A future improvement would be to use a more memory-efficient
 909             // selection model implementation when the number of items is large
 910             // (e.g. a simple List<Integer>, rather than a BitSet).
 911             return Integer.MAX_VALUE / 4;
 912         }
 913 
 914         @Override
 915         public void addListener(InvalidationListener listener) {
 916             // no-op
 917         }
 918 
 919         @Override
 920         public void removeListener(InvalidationListener listener) {
 921             // no-op
 922         }
 923 
 924         public void addListener(ListChangeListener<? super Integer> ll) {
 925             // no-op
 926         }
 927 
 928         public void removeListener(ListChangeListener<? super Integer> ll) {
 929             // no-op
 930         }
 931 
 932         public boolean addAll(Integer... es) {
 933             throw new UnsupportedOperationException("Not supported yet.");
 934         }
 935 
 936         public boolean setAll(Integer... es) {
 937             throw new UnsupportedOperationException("Not supported yet.");
 938         }
 939 
 940         public boolean setAll(Collection<? extends Integer> clctn) {
 941             throw new UnsupportedOperationException("Not supported yet.");
 942         }
 943 
 944         public boolean removeAll(Integer... es) {
 945             throw new UnsupportedOperationException("Not supported yet.");
 946         }
 947 
 948         public boolean retainAll(Integer... es) {
 949             throw new UnsupportedOperationException("Not supported yet.");
 950         }
 951 
 952         public void remove(int from, int to) {
 953             throw new UnsupportedOperationException("Not supported yet.");
 954         }
 955 
 956     }
 957 
 958     private static class MoneyFormatCell extends ListCell<Number> {
 959         @Override
 960         public void updateItem(Number item, boolean empty) {
 961             super.updateItem(item, empty);
 962 
 963             // format the number as if it were a monetary value using the
 964             // formatting relevant to the current locale. This would format
 965             // 43.68 as "$43.68", and -23.67 as "-$23.67"
 966             setText(item == null ? "" : NumberFormat.getCurrencyInstance().format(item));
 967 
 968             // change the label colour based on whether it is positive (green)
 969             // or negative (red). If the cell is selected, the text will
 970             // always be white (so that it can be read against the blue
 971             // background), and if the value is zero, we'll make it black.
 972             if (item != null) {
 973                 double value = item.doubleValue();
 974                 setTextFill(isSelected() ? Color.WHITE :
 975                     value == 0 ? Color.BLACK :
 976                     value < 0 ? Color.RED : Color.GREEN);
 977             }
 978         }
 979     }
 980 
 981 
 982 
 983     private static class ExpandOnSelectionCell<T> extends ListCell<T> {
 984         private static final double PREF_HEIGHT = 24;
 985         private static final double EXPAND_HEIGHT = PREF_HEIGHT * 3;
 986 
 987         private static final int duration = 350;
 988 
 989         // records all expanded cells
 990         private static final BitSet expandedCells = new BitSet();
 991 
 992 
 993         public ExpandOnSelectionCell() {
 994 
 995             prefHeightProperty().addListener(new InvalidationListener() {
 996                 public void invalidated(Observable o) {
 997                     requestLayout();
 998                 }
 999             });
1000 
1001             addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
1002                 @Override public void handle(MouseEvent t) {
1003 
1004                     double startHeight = getHeight();
1005 
1006                     // the end height is the opposite of the current state -
1007                     // we are animating out of this state after all
1008                     double endHeight = isExpanded() ? PREF_HEIGHT : EXPAND_HEIGHT;
1009 
1010                     // flip whether this cell index is expanded or not
1011                     expandedCells.flip(getIndex());
1012 
1013                     // create a timeline to expand/collapse the cell. All this
1014                     // really does is modify the height of the content
1015                     Timeline timeline = new Timeline();
1016                     timeline.setCycleCount(1);
1017                     timeline.setAutoReverse(false);
1018 
1019                     timeline.getKeyFrames().addAll(
1020                         new KeyFrame(Duration.ZERO, new KeyValue(prefHeightProperty(), startHeight, Interpolator.EASE_BOTH)),
1021                         new KeyFrame(Duration.millis(duration), new KeyValue(prefHeightProperty(), endHeight, Interpolator.EASE_BOTH))
1022                     );
1023 
1024                     timeline.playFromStart();
1025                 }
1026             });
1027         }
1028 
1029         @Override
1030         public void updateItem(T item, boolean empty) {
1031             super.updateItem(item, empty);
1032             setText(item == null ? "null" : item.toString());
1033         }
1034 
1035         @Override
1036         public void updateIndex(int i) {
1037             super.updateIndex(i);
1038 
1039             if (isExpanded()) {
1040                 // immediately expand this cell
1041                 setPrefHeight(EXPAND_HEIGHT);
1042             } else {
1043                 // immediately collapse this cell
1044                 setPrefHeight(PREF_HEIGHT);
1045             }
1046         }
1047 
1048         private boolean isExpanded() {
1049             if (getIndex() < 0) return false;
1050             return expandedCells.get(getIndex());
1051         }
1052 
1053         @Override
1054         protected double computePrefHeight(double width) {
1055             double ph = 0;
1056             if (isExpanded()) {
1057                 ph = getPrefHeight();
1058             } else {
1059                 ph = super.computePrefHeight(width);
1060             }
1061 
1062             return ph;
1063         }
1064 
1065         @Override protected double computeMinHeight(double width) {
1066             return computePrefHeight(width);
1067         }
1068     }
1069 }
1070