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