1 /*
   2  * Copyright (c) 2014, 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 package javafx.scene.control.test.tableview;
  26 
  27 import javafx.scene.control.skin.NestedTableColumnHeader;
  28 import javafx.scene.control.skin.TableColumnHeader;
  29 import javafx.scene.control.skin.TableHeaderRow;
  30 import javafx.scene.control.skin.TableViewSkin;
  31 import java.util.ArrayList;
  32 import java.util.HashMap;
  33 import java.util.Map;
  34 import javafx.beans.property.SimpleStringProperty;
  35 import javafx.beans.property.StringProperty;
  36 import javafx.beans.value.ChangeListener;
  37 import javafx.beans.value.ObservableValue;
  38 import javafx.collections.FXCollections;
  39 import javafx.collections.ObservableList;
  40 import javafx.collections.transformation.SortedList;
  41 import javafx.event.ActionEvent;
  42 import javafx.event.Event;
  43 import javafx.event.EventHandler;
  44 import javafx.geometry.Orientation;
  45 import javafx.scene.Scene;
  46 import javafx.scene.control.*;
  47 import javafx.scene.control.TableColumn.CellDataFeatures;
  48 import javafx.scene.control.cell.ChoiceBoxTableCell;
  49 import javafx.scene.control.cell.ComboBoxTableCell;
  50 import javafx.scene.control.cell.TextFieldTableCell;
  51 import javafx.scene.control.test.treetable.ResetButtonNames;
  52 import javafx.scene.control.test.utils.CommonPropertiesScene;
  53 import javafx.scene.control.test.utils.ComponentsFactory.MultipleIndexFormComponent;
  54 import javafx.scene.control.test.utils.ptables.NodeControllerFactory;
  55 import javafx.scene.control.test.utils.ptables.PropertiesTable;
  56 import javafx.scene.control.test.utils.ptables.PropertyTablesFactory;
  57 import javafx.scene.control.test.utils.ptables.TabPaneWithControl;
  58 import javafx.scene.input.KeyCode;
  59 import javafx.scene.input.KeyEvent;
  60 import javafx.scene.layout.HBox;
  61 import javafx.scene.layout.Pane;
  62 import javafx.scene.layout.VBox;
  63 import javafx.util.Callback;
  64 import test.javaclient.shared.InteroperabilityApp;
  65 import test.javaclient.shared.Utils;
  66 import static javafx.commons.Consts.*;
  67 import static javafx.commons.Consts.Cell.*;
  68 import javafx.scene.Node;
  69 import static javafx.scene.control.test.treetable.ResetButtonNames.HARD_RESET_BUTTON_ID;
  70 import static javafx.scene.control.test.treetable.ResetButtonNames.SOFT_RESET_BUTTON_ID;
  71 import javafx.scene.control.test.utils.ComponentsFactory.MultipleIndexFormComponent.MultipleIndicesAction;
  72 import static org.junit.Assert.assertTrue;
  73 
  74 /**
  75  * @author Alexander Kirov
  76  */
  77 public class NewTableViewApp extends InteroperabilityApp implements ResetButtonNames {
  78 
  79     public final static String TESTED_TABLE_VIEW_ID = "TESTED_TABLEVIEW_ID";
  80 
  81     public static void main(String[] args) {
  82         Utils.launch(NewTableViewApp.class, args);
  83     }
  84 
  85     @Override
  86     protected Scene getScene() {
  87         Utils.setTitleToStage(stage, "TableViewTestApp");
  88         Scene scene = new TableViewScene();
  89         Utils.addBrowser(scene);
  90         return scene;
  91     }
  92 
  93     class TableViewScene extends CommonPropertiesScene {
  94 
  95         //TabPane with properties tables.
  96         TabPaneWithControl tabPane;
  97         //Tableview to be tested.
  98         TableView<DataItem> testedTableView;
  99         //Container for all the data.
 100         ObservableList<DataItem> allData = FXCollections.observableArrayList();
 101         //List of existing columnsin current tableView.
 102         Map<String, TableColumn> existingColumns = new HashMap<String, TableColumn>();
 103         //This list contains all properties tables, which were created during testing.
 104         ArrayList<PropertiesTable> allPropertiesTables = new ArrayList<PropertiesTable>();
 105         private PropertiesTable tb;
 106 
 107         public TableViewScene() {
 108             super("TableView", 800, 600);
 109             prepareScene();
 110         }
 111 
 112         @Override
 113         final protected void prepareScene() {
 114             testedTableView = new TableView<DataItem>(allData);
 115             testedTableView.setId(TESTED_TABLE_VIEW_ID);
 116 
 117             tb = new PropertiesTable(testedTableView);
 118 
 119             PropertyTablesFactory.explorePropertiesList(testedTableView, tb);
 120 
 121             tb.addCounter(COUNTER_EDIT_START);
 122             tb.addCounter(COUNTER_EDIT_COMMIT);
 123             tb.addCounter(COUNTER_EDIT_CANCEL);
 124             tb.addCounter(COUNTER_ON_SORT);
 125             testedTableView.setOnSort(new EventHandler<SortEvent<TableView<DataItem>>>() {
 126                 public void handle(SortEvent<TableView<DataItem>> event) {
 127                     tb.incrementCounter(COUNTER_ON_SORT);
 128                 }
 129             });
 130 
 131             tabPane = new TabPaneWithControl("TableView", tb);
 132             tabPane.setMinSize(1000, 1000);
 133 
 134             Button hardResetButton = new Button("Hard reset");
 135             hardResetButton.setId(HARD_RESET_BUTTON_ID);
 136             hardResetButton.setOnAction(new EventHandler<ActionEvent>() {
 137                 public void handle(ActionEvent t) {
 138                     HBox hb = (HBox) getRoot();
 139                     hb.getChildren().clear();
 140                     prepareMainSceneStructure();
 141                     prepareScene();
 142                 }
 143             });
 144 
 145             Button softResetButton = new Button("Soft reset");
 146             softResetButton.setId(SOFT_RESET_BUTTON_ID);
 147             softResetButton.setOnAction(new EventHandler<ActionEvent>() {
 148                 public void handle(ActionEvent t) {
 149                     TableView newOne = new TableView();
 150                     refreshProcedure(1);
 151                     allData.clear();
 152                     existingColumns.clear();
 153                     testedTableView.setPrefHeight(newOne.getPrefHeight());
 154                     testedTableView.setPrefWidth(newOne.getPrefWidth());
 155                     testedTableView.getColumns().clear();
 156                     testedTableView.setPlaceholder(newOne.getPlaceholder());
 157                     testedTableView.setEditable(newOne.isEditable());
 158                     testedTableView.setVisible(newOne.isVisible());
 159                     testedTableView.setTableMenuButtonVisible(newOne.isTableMenuButtonVisible());
 160                     testedTableView.setDisable(newOne.isDisable());
 161                     testedTableView.setContextMenu(newOne.getContextMenu());
 162                 }
 163             });
 164 
 165             HBox resetButtonsHBox = new HBox();
 166             resetButtonsHBox.getChildren().addAll(hardResetButton, softResetButton);
 167 
 168             VBox vb = new VBox();
 169             vb.setSpacing(5);
 170             vb.getChildren().addAll(resetButtonsHBox, new Separator(Orientation.HORIZONTAL),
 171                     getAddColumnForm(), new Separator(Orientation.HORIZONTAL),
 172                     getChangeDataSizeForm(), new Separator(Orientation.HORIZONTAL),
 173                     getRemoveColumnsVBox(), new Separator(Orientation.HORIZONTAL),
 174                     getRemoveDataVBox(), new Separator(Orientation.HORIZONTAL),
 175                     getAddNestedColumnVBox(), new Separator(Orientation.HORIZONTAL),
 176                     controlsForEditing(), new Separator(Orientation.HORIZONTAL),
 177                     getReplaceTableHeaderImplementationButton(), new Separator(Orientation.HORIZONTAL));
 178             setControllersContent(vb);
 179             setPropertiesContent(tabPane);
 180             setTestedControl(testedTableView);
 181         }
 182 
 183         /*
 184          * In this function TabPane with control will be refreshed, and all
 185          * properties table will be refreshed and cleared.
 186          */
 187         private void refreshProcedure(int exceptFirstPropertiesTable) {
 188             for (int i = allPropertiesTables.size() - 1; i >= 0; i--) {
 189                 allPropertiesTables.get(i).refresh();
 190                 if (i >= exceptFirstPropertiesTable) {
 191                     allPropertiesTables.remove(i);
 192                 }
 193             }
 194 
 195             tabPane.removePropertiesTablesExceptFirstOnes(exceptFirstPropertiesTable);
 196         }
 197 
 198         private HBox getChangeDataSizeForm() {
 199             final TextField sizeTf = new TextField();
 200             sizeTf.setId(NEW_DATA_SIZE_TEXTFIELD_ID);
 201             sizeTf.setPromptText("new size (rows)");
 202 
 203             Button button = new Button("set");
 204             button.setId(NEW_DATA_SIZE_BUTTON_ID);
 205             button.setOnAction(new EventHandler<ActionEvent>() {
 206                 public void handle(ActionEvent t) {
 207                     int actualSize = allData.size();
 208                     int newSize = Integer.parseInt(sizeTf.getText());
 209                     if (actualSize > newSize) {
 210                         int toRemove = actualSize - newSize;
 211                         for (int i = 0; i < toRemove; i++) {
 212                             allData.remove(0);
 213                         }
 214                     } else if (actualSize < newSize) {
 215                         int toAdd = newSize - actualSize;
 216                         for (int i = 0; i < toAdd; i++) {
 217                             DataItem dataItem = new DataItem();
 218                             for (String columnName : existingColumns.keySet()) {
 219                                 dataItem.add(columnName, new SimpleStringProperty(columnName + "-" + String.valueOf(actualSize + i)));
 220                             }
 221                             allData.add(dataItem);
 222                         }
 223                     }
 224                 }
 225             });
 226 
 227             HBox hb1 = new HBox(3);
 228             hb1.getChildren().addAll(new Label("New size:"), sizeTf, button);
 229 
 230             Button addSortableRows = new Button("Add sortable rows");
 231             addSortableRows.setId(BTN_CREATE_SORTABLE_ROWS_ID);
 232             addSortableRows.setOnAction(new EventHandler<ActionEvent>() {
 233                 public void handle(ActionEvent t) {
 234                     allData.clear();
 235                     ObservableList<TableColumn<DataItem, ?>> columns = testedTableView.getColumns();
 236 
 237                     String[][] testData = getDataForSorting(columns.size());
 238 
 239                     for (int x = 0; x < testData.length; x++) {
 240                         DataItem dataItem = new DataItem();
 241                         int z = 0;
 242                         //Use the correct order of columns as they are placed.
 243                         //From left to right
 244                         for (int i = 0; i < testData[i].length; i++) {
 245                             TableColumn col = columns.get(i);
 246                             dataItem.add(col.getText(), new SimpleStringProperty(testData[x][z++]));
 247                         }
 248                         allData.add(dataItem);
 249                     }
 250                 }
 251             });
 252 
 253             Button setSortedListForModel = new Button("Change model to sortable list");
 254             setSortedListForModel.setId(BTN_SET_SORTED_LIST_FOR_MODEL_ID);
 255             setSortedListForModel.setOnAction((e) -> {
 256                 SortedList sortedList = new SortedList(allData);
 257                 sortedList.comparatorProperty().bind(testedTableView.comparatorProperty());
 258                 testedTableView.setItems(sortedList);
 259             });
 260 
 261             HBox topContainer = new HBox();
 262 
 263             VBox internalContainer = new VBox(5.0);
 264             internalContainer.getChildren().addAll(hb1, addSortableRows, setSortedListForModel);
 265 
 266             topContainer.getChildren().add(internalContainer);
 267             return topContainer;
 268 //            return hb1;
 269         }
 270 
 271         private VBox getAddColumnForm() {
 272             final TextField tfColumnName = new TextField();
 273             tfColumnName.setPromptText("new column name");
 274             tfColumnName.setId(NEW_COLUMN_NAME_TEXTFIELD_ID);
 275 
 276             final TextField indexTf = new TextField();
 277             indexTf.setPromptText("at index");
 278             indexTf.setId(NEW_COLUMN_INDEX_TEXTFIELD_UD);
 279 
 280             final CheckBox addDataPropertiesTabToTab = new CheckBox("with properties table for data");
 281             addDataPropertiesTabToTab.setId(NEW_COLUMN_GET_DATA_PROPERTIES_TAB_ID);
 282 
 283             final CheckBox addColumnPropertiesTableToTab = new CheckBox("with properties table for column");
 284             addColumnPropertiesTableToTab.setId(NEW_COLUMN_GET_COLUMN_PROPERTIES_TAB_ID);
 285 
 286             Button button = new Button("Add");
 287             button.setId(NEW_COLUMN_ADD_BUTTON_ID);
 288             button.setOnAction(new EventHandler<ActionEvent>() {
 289                 public void handle(ActionEvent t) {
 290                     final String name = tfColumnName.getText();
 291                     int index = Integer.parseInt(indexTf.getText());
 292                     TableColumn column = new TableColumn(name);
 293                     column.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<DataItem, String>, ObservableValue<String>>() {
 294                         public ObservableValue<String> call(CellDataFeatures<DataItem, String> p) {
 295                             return p.getValue().get(name);
 296                         }
 297                     });
 298                     introduceColumn(name, column);
 299                     if (addColumnPropertiesTableToTab.isSelected()) {
 300                         tabPane.addPropertiesTable(name, NodeControllerFactory.createFullController(column, tabPane));
 301                     }
 302                     testedTableView.getColumns().add(index, column);
 303                     if (addDataPropertiesTabToTab.isSelected()) {
 304                         final PropertiesTable forData = new PropertiesTable(null);
 305                         for (DataItem item : allData) {
 306                             forData.addStringLine(item.get(name), "new value");
 307                         }
 308                         tabPane.addPropertiesTable(name + " data", forData.getVisualRepresentation());
 309                     }
 310                 }
 311             });
 312 
 313             HBox hb1 = new HBox(3);
 314             hb1.getChildren().addAll(new Label("With name"), tfColumnName);
 315 
 316             HBox hb2 = new HBox(3);
 317             hb2.getChildren().addAll(new Label("At index"), indexTf, button);
 318 
 319             VBox vb = new VBox();
 320             vb.getChildren().addAll(hb1, addDataPropertiesTabToTab, addColumnPropertiesTableToTab, hb2);
 321             return vb;
 322         }
 323 
 324         Node getReplaceTableHeaderImplementationButton() {
 325             Button replaceButton = new Button("Replace skin implementation");
 326             replaceButton.setId(REPLACE_SKIN_IMPLEMENTATION_BUTTON_ID);
 327             replaceButton.setOnAction(new EventHandler<ActionEvent>() {
 328                 public void handle(ActionEvent t) {
 329                     testedTableView.setSkin(new TableViewSkin(testedTableView) {
 330                         @Override
 331                         public String toString() {
 332                             return "CUSTOM " + super.toString();
 333                         }
 334 /*
 335                         @Override
 336                         protected TableHeaderRow createTableHeaderRow() {
 337                             return new TableHeaderRow(this) {
 338                                 @Override
 339                                 protected NestedTableColumnHeader createRootHeader() {
 340                                     return new NestedTableColumnHeader((TableViewSkin) testedTableView.getSkin(), null) {
 341                                         @Override
 342                                         protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
 343                                             if (col.getColumns().isEmpty()) {
 344                                                 final TableColumnHeader tableColumnHeader = new TableColumnHeader(getTableViewSkin(), col);
 345                                                 tableColumnHeader.setId(CUSTOM_IMPLEMENTATION_MARKER);
 346                                                 return tableColumnHeader;
 347                                             } else {
 348                                                 final NestedTableColumnHeader nestedTableColumnHeader = new NestedTableColumnHeader(getTableViewSkin(), col);
 349                                                 nestedTableColumnHeader.setId(CUSTOM_IMPLEMENTATION_MARKER);
 350                                                 return nestedTableColumnHeader;
 351                                             }
 352                                         }
 353                                     };
 354                                 }
 355                             };
 356                         }
 357                         */
 358                     });
 359                 }
 360             });
 361 
 362             return replaceButton;
 363         }
 364 
 365         VBox getAddNestedColumnVBox() {
 366             final TextField nestedColumnAtIndexTF = new TextField("0");
 367             nestedColumnAtIndexTF.setPromptText("index");
 368             nestedColumnAtIndexTF.setId(NESTED_COLUMN_INDEX_TEXT_FIELD_ID);
 369 
 370             final TextField nestedColumnNameTF = new TextField();
 371             nestedColumnNameTF.setPromptText("namex");
 372             nestedColumnNameTF.setId(NESTED_COLUMN_NAME_TEXT_FIELD_ID);
 373 
 374             HBox nestedColumnAdditionalNodes = new HBox();
 375             nestedColumnAdditionalNodes.getChildren().addAll(nestedColumnAtIndexTF, nestedColumnNameTF);
 376 
 377             MultipleIndicesAction executor = new MultipleIndexFormComponent.MultipleIndicesAction() {
 378                 public void onAction(int[] indices) {
 379                     int[] reversed = new int[indices.length];
 380                     for (int i = 0; i < indices.length; i++) {
 381                         reversed[i] = indices[indices.length - i - 1];
 382                     }
 383                     TableColumn nestedColumn = new TableColumn(nestedColumnNameTF.getText());
 384                     for (int i : indices) {
 385                         nestedColumn.getColumns().add(testedTableView.getColumns().get(i));
 386                     }
 387                     for (int i : reversed) {
 388                         testedTableView.getColumns().remove(i);
 389                     }
 390                     tabPane.addPropertiesTable(nestedColumnNameTF.getText(), NodeControllerFactory.createFullController(nestedColumn, tabPane));
 391                     testedTableView.getColumns().add(Integer.valueOf(nestedColumnAtIndexTF.getText()), nestedColumn);
 392                 }
 393             };
 394 
 395             MultipleIndexFormComponent multipleIndexForm = new MultipleIndexFormComponent(
 396                     "Add nested column",
 397                     nestedColumnAdditionalNodes,
 398                     executor,
 399                     CREATE_NESTED_COLUMN_MULTIPLE_ACTION_BUTTON_ID,
 400                     CREATE_NESTED_COLUMN_MULTIPLE_TEXTFIELD_ID);
 401 
 402             return multipleIndexForm;
 403         }
 404 
 405         VBox getRemoveColumnsVBox() {
 406             MultipleIndicesAction executor = new MultipleIndexFormComponent.MultipleIndicesAction() {
 407                 public void onAction(int[] indices) {
 408                     ObservableList onRemoving = FXCollections.observableArrayList();
 409                     for (int index : indices) {
 410                         onRemoving.add(testedTableView.getColumns().get(index));
 411                     }
 412                     testedTableView.getColumns().removeAll(onRemoving);
 413                 }
 414             };
 415             MultipleIndexFormComponent multipleIndexForm = new MultipleIndexFormComponent(
 416                     "Remove columns", null, executor,
 417                     REMOVE_MULTIPLE_COLUMNS_ACTION_BUTTON_ID,
 418                     REMOVE_MULTIPLE_COLUMNS_TEXT_FIELD_ID);
 419 
 420             return multipleIndexForm;
 421         }
 422 
 423         VBox getRemoveDataVBox() {
 424             MultipleIndicesAction executor = new MultipleIndexFormComponent.MultipleIndicesAction() {
 425                 public void onAction(int[] indices) {
 426                     ObservableList onRemoving = FXCollections.observableArrayList();
 427                     for (int index : indices) {
 428                         onRemoving.add(testedTableView.getItems().get(index));
 429                     }
 430                     testedTableView.getItems().removeAll(onRemoving);
 431                 }
 432             };
 433             MultipleIndexFormComponent multipleIndexForm = new MultipleIndexFormComponent(
 434                     "Remove data items", null, executor,
 435                     REMOVE_DATA_ITEMS_MULTIPLE_ACTION_BUTTON_ID,
 436                     REMOVE_DATA_ITEMS_MULTIPLE_TEXT_FIELD_ID);
 437 
 438             return multipleIndexForm;
 439         }
 440 
 441         private Pane controlsForEditing() {
 442 
 443             VBox topContainer = new VBox(3.5);
 444             HBox hb = new HBox(3.0);
 445 
 446             final ComboBox cmbEditors = new ComboBox();
 447             cmbEditors.setId(CMB_EDITORS_ID);
 448             cmbEditors.getItems().addAll((Object[]) CellEditorType.values());
 449 
 450             final CheckBox chbCustom = new CheckBox("Custom");
 451             chbCustom.setId(CHB_IS_CUSTOM_ID);
 452 
 453             Button btnSetEditor = new Button("Set editor");
 454             btnSetEditor.setId(BTN_SET_CELLS_EDITOR_ID);
 455             btnSetEditor.setOnAction(new EventHandler<ActionEvent>() {
 456                 public void handle(ActionEvent t) {
 457                     CellEditorType editor = (CellEditorType) cmbEditors.getSelectionModel().getSelectedItem();
 458                     setCellEditor(editor, chbCustom.isSelected());
 459                 }
 460             });
 461 
 462             hb.getChildren().addAll(cmbEditors, chbCustom, btnSetEditor);
 463 
 464             Button btn = new Button("Set onEdit event hadlers");
 465             btn.setId(BTN_SET_ON_EDIT_EVENT_HANDLERS);
 466 
 467             btn.setOnAction(new EventHandler<ActionEvent>() {
 468                 public void handle(ActionEvent t) {
 469                     final EventHandler eventHandlerOnEditStart = new EventHandler() {
 470                         public void handle(Event t) {
 471 //                                new Throwable().printStackTrace();
 472                             tb.incrementCounter(COUNTER_EDIT_START);
 473                         }
 474                     };
 475 
 476                     final EventHandler eventHandlerOnEditCommit = new EventHandler() {
 477                         public void handle(Event t) {
 478                             tb.incrementCounter(COUNTER_EDIT_COMMIT);
 479                         }
 480                     };
 481 
 482                     final EventHandler eventHandlerOnEditCancel = new EventHandler() {
 483                         public void handle(Event t) {
 484                             t.consume();
 485                             tb.incrementCounter(COUNTER_EDIT_CANCEL);
 486                         }
 487                     };
 488 
 489                     for (TableColumn col : testedTableView.getColumns()) {
 490                         col.setOnEditStart(eventHandlerOnEditStart);
 491                         assertTrue(eventHandlerOnEditStart == col.getOnEditStart());
 492 
 493                         col.setOnEditCommit(eventHandlerOnEditCommit);
 494                         assertTrue(eventHandlerOnEditCommit == col.getOnEditCommit());
 495 
 496                         col.setOnEditCancel(eventHandlerOnEditCancel);
 497                         assertTrue(eventHandlerOnEditCancel == col.getOnEditCancel());
 498                     }
 499                 }
 500             });
 501 
 502             topContainer.getChildren().addAll(hb, btn);
 503             return topContainer;
 504         }
 505 
 506         private void introduceColumn(String columnName, TableColumn column) {
 507             existingColumns.put(columnName, column);
 508             int counter = 0;
 509             for (DataItem item : allData) {
 510                 item.add(columnName, new SimpleStringProperty(columnName + "-" + String.valueOf(counter)));
 511                 counter++;
 512             }
 513         }
 514 
 515         /**
 516          * This class contain HashMap, which contain dynamically generated data.
 517          * For each line in the Table, when you add additional column, you
 518          * should add additional key-value pair in map for all dataItems in
 519          * allData observable list.
 520          */
 521         public class DataItem implements Comparable {
 522 
 523             private HashMap<String, StringProperty> data = new HashMap<String, StringProperty>();
 524 
 525             public void add(String string, StringProperty property) {
 526                 data.put(string, property);
 527             }
 528 
 529             public StringProperty get(String name) {
 530                 return data.get(name);
 531             }
 532 
 533             public int compareTo(Object that) {
 534                 if (this == that) {
 535                     return 0;
 536                 }
 537 
 538                 DataItem other = (DataItem) that;
 539 
 540                 if (data.size() != other.data.size()) {
 541                     throw new IllegalStateException("[All data items in the table must have equal number of fields]");
 542                 }
 543 
 544                 int res = 0;
 545 
 546                 for (String key : data.keySet()) {
 547                     res = data.get(key).get().compareTo(other.data.get(key).get());
 548                     if (res != 0) {
 549                         break;
 550                     }
 551                 }
 552 
 553                 return res;
 554             }
 555         }
 556 
 557         private void setCellEditor(CellEditorType editor, boolean isCustom) {
 558             if (null == editor) {
 559                 System.out.println("Editor is not selected");
 560                 return;
 561             }
 562 
 563             switch (editor) {
 564                 case TEXT_FIELD:
 565                     setTextFieldCellEditor(isCustom);
 566                     break;
 567                 case COMBOBOX:
 568                     setComboboxCellEditor(isCustom);
 569                     break;
 570                 case CHOICEBOX:
 571                     setChoiceboxCellEditor(isCustom);
 572                     break;
 573                 default:
 574                     throw new UnsupportedOperationException(editor.toString());
 575             }
 576 
 577             System.out.println(String.format("Editor set: %s %s",
 578                     isCustom ? "custom" : "",
 579                     editor.toString()));
 580         }
 581 
 582         private void setTextFieldCellEditor(boolean isCustom) {
 583             for (TableColumn col : testedTableView.getColumns()) {
 584                 if (!isCustom) {
 585                     col.setCellFactory(TextFieldTableCell.forTableColumn());
 586                 } else {
 587                     col.setCellFactory(new Callback() {
 588                         public Object call(Object p) {
 589                             return new EditingTextFieldCell();
 590                         }
 591                     });
 592                 }
 593             }
 594         }
 595 
 596         private void setComboboxCellEditor(boolean isCustom) {
 597 
 598             for (int i = 0; i < testedTableView.getColumns().size(); i++) {
 599                 TableColumn col = testedTableView.getColumns().get(i);
 600                 String colName = col.getText();
 601                 final ObservableList<String> items = FXCollections.observableArrayList();
 602 
 603                 for (DataItem dataItem : allData) {
 604                     items.add(dataItem.get(colName).get());
 605                 }
 606                 if (!isCustom) {
 607                     col.setCellFactory(ComboBoxTableCell.forTableColumn(items));
 608                 } else {
 609                     col.setCellFactory(new Callback() {
 610                         public Object call(Object p) {
 611                             return new EditingComboBoxCell(items);
 612                         }
 613                     });
 614                 }
 615             }
 616         }
 617 
 618         private void setChoiceboxCellEditor(boolean isCustom) {
 619 
 620             for (int i = 0; i < testedTableView.getColumns().size(); i++) {
 621                 TableColumn col = testedTableView.getColumns().get(i);
 622                 String colName = col.getText();
 623                 final ObservableList<String> items = FXCollections.observableArrayList();
 624 
 625                 for (DataItem dataItem : allData) {
 626                     items.add(dataItem.get(colName).get());
 627                 }
 628                 if (!isCustom) {
 629                     col.setCellFactory(ChoiceBoxTableCell.forTableColumn(items));
 630                 } else {
 631                     col.setCellFactory(new Callback() {
 632                         public Object call(Object p) {
 633                             return new EditingChoiceBoxCell(items);
 634                         }
 635                     });
 636                 }
 637             }
 638         }
 639 
 640         private class EditingTextFieldCell extends TableCell {
 641 
 642             private TextField textField;
 643 
 644             public EditingTextFieldCell() {
 645                 setId(EDITING_TEXTFIELD_CELL_ID);
 646             }
 647 
 648             @Override
 649             public void startEdit() {
 650                 if (!isEmpty()) {
 651                     super.startEdit();
 652                     createTextField();
 653 
 654                     setText(null);
 655                     setGraphic(textField);
 656                 }
 657             }
 658 
 659             @Override
 660             public void cancelEdit() {
 661                 super.cancelEdit();
 662 
 663                 setText(getString());
 664                 setGraphic(null);
 665             }
 666 
 667             @Override
 668             public void updateItem(Object item, boolean empty) {
 669                 super.updateItem(item, empty);
 670 
 671                 if (empty) {
 672                     setText(null);
 673                     setGraphic(null);
 674                 } else {
 675                     if (isEditing()) {
 676                         if (textField != null) {
 677                             textField.setText(getString());
 678                         }
 679                         setText(null);
 680                         setGraphic(textField);
 681                     } else {
 682                         setText(getString());
 683                         setGraphic(null);
 684                     }
 685                 }
 686             }
 687 
 688             private void createTextField() {
 689                 textField = new TextField(getString());
 690                 textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
 691 
 692                 textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
 693                     @Override
 694                     public void handle(KeyEvent t) {
 695                         if (t.getCode() == KeyCode.ENTER) {
 696                             commitEdit(textField.getText());
 697                         } else if (t.getCode() == KeyCode.ESCAPE) {
 698                             cancelEdit();
 699                         }
 700                     }
 701                 });
 702             }
 703 
 704             private String getString() {
 705                 return getItem() == null ? "" : getItem().toString();
 706             }
 707         }
 708 
 709         private class EditingComboBoxCell extends TableCell {
 710 
 711             ObservableList items;
 712             ComboBox comboBox;
 713 
 714             public EditingComboBoxCell(ObservableList _items) {
 715                 items = _items;
 716                 setId(EDITING_COMBOBOX_CELL_ID);
 717             }
 718 
 719             @Override
 720             public void startEdit() {
 721                 if (isEmpty()) {
 722                     return;
 723                 }
 724                 createComboBox();
 725                 comboBox.getSelectionModel().select(getItem());
 726 
 727                 super.startEdit();
 728 
 729                 setText(null);
 730                 setGraphic(comboBox);
 731             }
 732 
 733             @Override
 734             public void cancelEdit() {
 735                 super.cancelEdit();
 736                 setGraphic(null);
 737                 setText(getString());
 738             }
 739 
 740             @Override
 741             public void updateItem(Object item, boolean isEmpty) {
 742                 super.updateItem(item, isEmpty);
 743                 if (isEmpty()) {
 744                     setText(null);
 745                     setGraphic(null);
 746                 } else {
 747                     if (isEditing()) {
 748                         if (comboBox != null) {
 749                             comboBox.getSelectionModel().select(getItem());
 750                         }
 751                         setText(null);
 752                         setGraphic(comboBox);
 753                     } else {
 754                         setText(getString());
 755                         setGraphic(null);
 756                     }
 757                 }
 758             }
 759 
 760             private String getString() {
 761                 return getItem() == null ? "" : getItem().toString();
 762             }
 763 
 764             private void createComboBox() {
 765                 if (null == comboBox) {
 766                     comboBox = new ComboBox(items);
 767                     comboBox.setMaxWidth(Double.MAX_VALUE);
 768                     comboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
 769                         @Override
 770                         public void changed(ObservableValue ov, Object oldValue, Object newValue) {
 771                             if (isEditing()) {
 772                                 commitEdit(newValue);
 773                             }
 774                         }
 775                     });
 776                 }
 777             }
 778         }
 779 
 780         private class EditingChoiceBoxCell extends TableCell {
 781 
 782             ObservableList items;
 783             ChoiceBox choiceBox;
 784 
 785             public EditingChoiceBoxCell(ObservableList _items) {
 786                 items = _items;
 787                 setId(EDITING_CHOICEBOX_CELL_ID);
 788             }
 789 
 790             @Override
 791             public void startEdit() {
 792                 if (isEmpty()) {
 793                     return;
 794                 }
 795 
 796                 createComboBox();
 797 
 798                 choiceBox.getSelectionModel().select(getItem());
 799 
 800                 super.startEdit();
 801                 setText(null);
 802                 setGraphic(choiceBox);
 803             }
 804 
 805             @Override
 806             public void cancelEdit() {
 807                 super.cancelEdit();
 808                 setGraphic(null);
 809                 setText(getString());
 810             }
 811 
 812             @Override
 813             public void updateItem(Object item, boolean isEmpty) {
 814                 super.updateItem(item, isEmpty);
 815                 if (isEmpty()) {
 816                     setText(null);
 817                     setGraphic(null);
 818                 } else {
 819                     if (isEditing()) {
 820                         if (choiceBox != null) {
 821                             choiceBox.getSelectionModel().select(getItem());
 822                         }
 823                         setText(null);
 824                         setGraphic(choiceBox);
 825                     } else {
 826                         setText(getString());
 827                         setGraphic(null);
 828                     }
 829                 }
 830             }
 831 
 832             private String getString() {
 833                 return getItem() == null ? "" : getItem().toString();
 834             }
 835 
 836             private void createComboBox() {
 837                 if (null == choiceBox) {
 838                     choiceBox = new ChoiceBox(items);
 839                     choiceBox.setMaxWidth(Double.MAX_VALUE);
 840                     choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
 841                         @Override
 842                         public void changed(ObservableValue ov, Object oldValue, Object newValue) {
 843                             if (isEditing()) {
 844                                 commitEdit(newValue);
 845                             }
 846                         }
 847                     });
 848                 }
 849             }
 850         }
 851     }
 852 
 853     /**
 854      * Produces 2D array of strings to facilitate multiple columns sorting
 855      * tests.
 856      *
 857      * @param COLS number of columns in the table
 858      * @return 2D array of strings
 859      */
 860     public static String[][] getDataForSorting(final int COLS) {
 861         int rows;
 862         if (COLS < 32) {
 863             rows = 1 << COLS;
 864         } else {
 865             rows = (int) Math.pow(2, COLS);
 866         }
 867 
 868         char alphabet[] = new char[52];
 869         for (int i = 0; i < 26; i++) {
 870             alphabet[i] = (char) ('A' + i);
 871         }
 872         for (int i = 0; i < 26; i++) {
 873             alphabet[i + 26] = (char) ('a' + i);
 874         }
 875 
 876         String[][] testData = new String[rows][];
 877         for (int x = 0; x < rows; x++) {
 878 
 879             testData[x] = new String[COLS];
 880 
 881             for (int y = 0; y < COLS; y++) {
 882                 int pos = x / (rows >> y + 1);
 883                 if (pos >= 52) {
 884 
 885                     StringBuilder sb = new StringBuilder();
 886 
 887                     int quotient = pos / 52;
 888                     while (quotient-- > 0) {
 889                         sb.append("z");
 890                     }
 891 
 892                     sb.append(alphabet[pos % 52]);
 893                     testData[x][y] = sb.toString();
 894 
 895                 } else {
 896                     testData[x][y] = String.valueOf(alphabet[pos]);
 897                 }
 898             }
 899         }
 900         return testData;
 901     }
 902 }