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             return replaceButton;
 362         }
 363 
 364         VBox getAddNestedColumnVBox() {
 365             final TextField nestedColumnAtIndexTF = new TextField("0");
 366             nestedColumnAtIndexTF.setPromptText("index");
 367             nestedColumnAtIndexTF.setId(NESTED_COLUMN_INDEX_TEXT_FIELD_ID);
 368 
 369             final TextField nestedColumnNameTF = new TextField();
 370             nestedColumnNameTF.setPromptText("namex");
 371             nestedColumnNameTF.setId(NESTED_COLUMN_NAME_TEXT_FIELD_ID);
 372 
 373             HBox nestedColumnAdditionalNodes = new HBox();
 374             nestedColumnAdditionalNodes.getChildren().addAll(nestedColumnAtIndexTF, nestedColumnNameTF);
 375 
 376             MultipleIndicesAction executor = new MultipleIndexFormComponent.MultipleIndicesAction() {
 377                 public void onAction(int[] indices) {
 378                     int[] reversed = new int[indices.length];
 379                     for (int i = 0; i < indices.length; i++) {
 380                         reversed[i] = indices[indices.length - i - 1];
 381                     }
 382                     TableColumn nestedColumn = new TableColumn(nestedColumnNameTF.getText());
 383                     for (int i : indices) {
 384                         nestedColumn.getColumns().add(testedTableView.getColumns().get(i));
 385                     }
 386                     for (int i : reversed) {
 387                         testedTableView.getColumns().remove(i);
 388                     }
 389                     tabPane.addPropertiesTable(nestedColumnNameTF.getText(), NodeControllerFactory.createFullController(nestedColumn, tabPane));
 390                     testedTableView.getColumns().add(Integer.valueOf(nestedColumnAtIndexTF.getText()), nestedColumn);
 391                 }
 392             };
 393 
 394             MultipleIndexFormComponent multipleIndexForm = new MultipleIndexFormComponent(
 395                     "Add nested column",
 396                     nestedColumnAdditionalNodes,
 397                     executor,
 398                     CREATE_NESTED_COLUMN_MULTIPLE_ACTION_BUTTON_ID,
 399                     CREATE_NESTED_COLUMN_MULTIPLE_TEXTFIELD_ID);
 400 
 401             return multipleIndexForm;
 402         }
 403 
 404         VBox getRemoveColumnsVBox() {
 405             MultipleIndicesAction executor = new MultipleIndexFormComponent.MultipleIndicesAction() {
 406                 public void onAction(int[] indices) {
 407                     ObservableList onRemoving = FXCollections.observableArrayList();
 408                     for (int index : indices) {
 409                         onRemoving.add(testedTableView.getColumns().get(index));
 410                     }
 411                     testedTableView.getColumns().removeAll(onRemoving);
 412                 }
 413             };
 414             MultipleIndexFormComponent multipleIndexForm = new MultipleIndexFormComponent(
 415                     "Remove columns", null, executor,
 416                     REMOVE_MULTIPLE_COLUMNS_ACTION_BUTTON_ID,
 417                     REMOVE_MULTIPLE_COLUMNS_TEXT_FIELD_ID);
 418 
 419             return multipleIndexForm;
 420         }
 421 
 422         VBox getRemoveDataVBox() {
 423             MultipleIndicesAction executor = new MultipleIndexFormComponent.MultipleIndicesAction() {
 424                 public void onAction(int[] indices) {
 425                     ObservableList onRemoving = FXCollections.observableArrayList();
 426                     for (int index : indices) {
 427                         onRemoving.add(testedTableView.getItems().get(index));
 428                     }
 429                     testedTableView.getItems().removeAll(onRemoving);
 430                 }
 431             };
 432             MultipleIndexFormComponent multipleIndexForm = new MultipleIndexFormComponent(
 433                     "Remove data items", null, executor,
 434                     REMOVE_DATA_ITEMS_MULTIPLE_ACTION_BUTTON_ID,
 435                     REMOVE_DATA_ITEMS_MULTIPLE_TEXT_FIELD_ID);
 436 
 437             return multipleIndexForm;
 438         }
 439 
 440         private Pane controlsForEditing() {
 441 
 442             VBox topContainer = new VBox(3.5);
 443             HBox hb = new HBox(3.0);
 444 
 445             final ComboBox cmbEditors = new ComboBox();
 446             cmbEditors.setId(CMB_EDITORS_ID);
 447             cmbEditors.getItems().addAll((Object[]) CellEditorType.values());
 448 
 449             final CheckBox chbCustom = new CheckBox("Custom");
 450             chbCustom.setId(CHB_IS_CUSTOM_ID);
 451 
 452             Button btnSetEditor = new Button("Set editor");
 453             btnSetEditor.setId(BTN_SET_CELLS_EDITOR_ID);
 454             btnSetEditor.setOnAction(new EventHandler<ActionEvent>() {
 455                 public void handle(ActionEvent t) {
 456                     CellEditorType editor = (CellEditorType) cmbEditors.getSelectionModel().getSelectedItem();
 457                     setCellEditor(editor, chbCustom.isSelected());
 458                 }
 459             });
 460 
 461             hb.getChildren().addAll(cmbEditors, chbCustom, btnSetEditor);
 462 
 463             Button btn = new Button("Set onEdit event hadlers");
 464             btn.setId(BTN_SET_ON_EDIT_EVENT_HANDLERS);
 465 
 466             btn.setOnAction(new EventHandler<ActionEvent>() {
 467                 public void handle(ActionEvent t) {
 468                     final EventHandler eventHandlerOnEditStart = new EventHandler() {
 469                         public void handle(Event t) {
 470 //                                new Throwable().printStackTrace();
 471                             tb.incrementCounter(COUNTER_EDIT_START);
 472                         }
 473                     };
 474                     
 475                     final EventHandler eventHandlerOnEditCommit = new EventHandler() {
 476                         public void handle(Event t) {
 477                             tb.incrementCounter(COUNTER_EDIT_COMMIT);
 478                         }
 479                     };
 480                     
 481                     final EventHandler eventHandlerOnEditCancel = new EventHandler() {
 482                         public void handle(Event t) {
 483                             t.consume();
 484                             tb.incrementCounter(COUNTER_EDIT_CANCEL);
 485                         }
 486                     };
 487                     
 488                     for (TableColumn col : testedTableView.getColumns()) {
 489                         col.setOnEditStart(eventHandlerOnEditStart);
 490                         assertTrue(eventHandlerOnEditStart == col.getOnEditStart());
 491 
 492                         col.setOnEditCommit(eventHandlerOnEditCommit);
 493                         assertTrue(eventHandlerOnEditCommit == col.getOnEditCommit());
 494 
 495                         col.setOnEditCancel(eventHandlerOnEditCancel);
 496                         assertTrue(eventHandlerOnEditCancel == col.getOnEditCancel());
 497                     }
 498                 }
 499             });
 500 
 501             topContainer.getChildren().addAll(hb, btn);
 502             return topContainer;
 503         }
 504 
 505         private void introduceColumn(String columnName, TableColumn column) {
 506             existingColumns.put(columnName, column);
 507             int counter = 0;
 508             for (DataItem item : allData) {
 509                 item.add(columnName, new SimpleStringProperty(columnName + "-" + String.valueOf(counter)));
 510                 counter++;
 511             }
 512         }
 513 
 514         /**
 515          * This class contain HashMap, which contain dynamically generated data.
 516          * For each line in the Table, when you add additional column, you
 517          * should add additional key-value pair in map for all dataItems in
 518          * allData observable list.
 519          */
 520         public class DataItem implements Comparable {
 521 
 522             private HashMap<String, StringProperty> data = new HashMap<String, StringProperty>();
 523 
 524             public void add(String string, StringProperty property) {
 525                 data.put(string, property);
 526             }
 527 
 528             public StringProperty get(String name) {
 529                 return data.get(name);
 530             }
 531 
 532             public int compareTo(Object that) {
 533                 if (this == that) {
 534                     return 0;
 535                 }
 536 
 537                 DataItem other = (DataItem) that;
 538 
 539                 if (data.size() != other.data.size()) {
 540                     throw new IllegalStateException("[All data items in the table must have equal number of fields]");
 541                 }
 542 
 543                 int res = 0;
 544 
 545                 for (String key : data.keySet()) {
 546                     res = data.get(key).get().compareTo(other.data.get(key).get());
 547                     if (res != 0) {
 548                         break;
 549                     }
 550                 }
 551 
 552                 return res;
 553             }
 554         }
 555 
 556         private void setCellEditor(CellEditorType editor, boolean isCustom) {
 557             if (null == editor) {
 558                 System.out.println("Editor is not selected");
 559                 return;
 560             }
 561 
 562             switch (editor) {
 563                 case TEXT_FIELD:
 564                     setTextFieldCellEditor(isCustom);
 565                     break;
 566                 case COMBOBOX:
 567                     setComboboxCellEditor(isCustom);
 568                     break;
 569                 case CHOICEBOX:
 570                     setChoiceboxCellEditor(isCustom);
 571                     break;
 572                 default:
 573                     throw new UnsupportedOperationException(editor.toString());
 574             }
 575 
 576             System.out.println(String.format("Editor set: %s %s",
 577                     isCustom ? "custom" : "",
 578                     editor.toString()));
 579         }
 580 
 581         private void setTextFieldCellEditor(boolean isCustom) {
 582             for (TableColumn col : testedTableView.getColumns()) {
 583                 if (!isCustom) {
 584                     col.setCellFactory(TextFieldTableCell.forTableColumn());
 585                 } else {
 586                     col.setCellFactory(new Callback() {
 587                         public Object call(Object p) {
 588                             return new EditingTextFieldCell();
 589                         }
 590                     });
 591                 }
 592             }
 593         }
 594 
 595         private void setComboboxCellEditor(boolean isCustom) {
 596 
 597             for (int i = 0; i < testedTableView.getColumns().size(); i++) {
 598                 TableColumn col = testedTableView.getColumns().get(i);
 599                 String colName = col.getText();
 600                 final ObservableList<String> items = FXCollections.observableArrayList();
 601 
 602                 for (DataItem dataItem : allData) {
 603                     items.add(dataItem.get(colName).get());
 604                 }
 605                 if (!isCustom) {
 606                     col.setCellFactory(ComboBoxTableCell.forTableColumn(items));
 607                 } else {
 608                     col.setCellFactory(new Callback() {
 609                         public Object call(Object p) {
 610                             return new EditingComboBoxCell(items);
 611                         }
 612                     });
 613                 }
 614             }
 615         }
 616 
 617         private void setChoiceboxCellEditor(boolean isCustom) {
 618 
 619             for (int i = 0; i < testedTableView.getColumns().size(); i++) {
 620                 TableColumn col = testedTableView.getColumns().get(i);
 621                 String colName = col.getText();
 622                 final ObservableList<String> items = FXCollections.observableArrayList();
 623 
 624                 for (DataItem dataItem : allData) {
 625                     items.add(dataItem.get(colName).get());
 626                 }
 627                 if (!isCustom) {
 628                     col.setCellFactory(ChoiceBoxTableCell.forTableColumn(items));
 629                 } else {
 630                     col.setCellFactory(new Callback() {
 631                         public Object call(Object p) {
 632                             return new EditingChoiceBoxCell(items);
 633                         }
 634                     });
 635                 }
 636             }
 637         }
 638 
 639         private class EditingTextFieldCell extends TableCell {
 640 
 641             private TextField textField;
 642 
 643             public EditingTextFieldCell() {
 644                 setId(EDITING_TEXTFIELD_CELL_ID);
 645             }
 646 
 647             @Override
 648             public void startEdit() {
 649                 if (!isEmpty()) {
 650                     super.startEdit();
 651                     createTextField();
 652 
 653                     setText(null);
 654                     setGraphic(textField);
 655                 }
 656             }
 657 
 658             @Override
 659             public void cancelEdit() {
 660                 super.cancelEdit();
 661 
 662                 setText(getString());
 663                 setGraphic(null);
 664             }
 665 
 666             @Override
 667             public void updateItem(Object item, boolean empty) {
 668                 super.updateItem(item, empty);
 669 
 670                 if (empty) {
 671                     setText(null);
 672                     setGraphic(null);
 673                 } else {
 674                     if (isEditing()) {
 675                         if (textField != null) {
 676                             textField.setText(getString());
 677                         }
 678                         setText(null);
 679                         setGraphic(textField);
 680                     } else {
 681                         setText(getString());
 682                         setGraphic(null);
 683                     }
 684                 }
 685             }
 686 
 687             private void createTextField() {
 688                 textField = new TextField(getString());
 689                 textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
 690 
 691                 textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
 692                     @Override
 693                     public void handle(KeyEvent t) {
 694                         if (t.getCode() == KeyCode.ENTER) {
 695                             commitEdit(textField.getText());
 696                         } else if (t.getCode() == KeyCode.ESCAPE) {
 697                             cancelEdit();
 698                         }
 699                     }
 700                 });
 701             }
 702 
 703             private String getString() {
 704                 return getItem() == null ? "" : getItem().toString();
 705             }
 706         }
 707 
 708         private class EditingComboBoxCell extends TableCell {
 709 
 710             ObservableList items;
 711             ComboBox comboBox;
 712 
 713             public EditingComboBoxCell(ObservableList _items) {
 714                 items = _items;
 715                 setId(EDITING_COMBOBOX_CELL_ID);
 716             }
 717 
 718             @Override
 719             public void startEdit() {
 720                 if (isEmpty()) {
 721                     return;
 722                 }
 723                 createComboBox();
 724                 comboBox.getSelectionModel().select(getItem());
 725                 
 726                 super.startEdit();
 727                 
 728                 setText(null);
 729                 setGraphic(comboBox);
 730             }
 731 
 732             @Override
 733             public void cancelEdit() {
 734                 super.cancelEdit();
 735                 setGraphic(null);
 736                 setText(getString());
 737             }
 738 
 739             @Override
 740             public void updateItem(Object item, boolean isEmpty) {
 741                 super.updateItem(item, isEmpty);
 742                 if (isEmpty()) {
 743                     setText(null);
 744                     setGraphic(null);
 745                 } else {
 746                     if (isEditing()) {
 747                         if (comboBox != null) {
 748                             comboBox.getSelectionModel().select(getItem());
 749                         }
 750                         setText(null);
 751                         setGraphic(comboBox);
 752                     } else {
 753                         setText(getString());
 754                         setGraphic(null);
 755                     }
 756                 }
 757             }
 758 
 759             private String getString() {
 760                 return getItem() == null ? "" : getItem().toString();
 761             }
 762 
 763             private void createComboBox() {
 764                 if (null == comboBox) {
 765                     comboBox = new ComboBox(items);
 766                     comboBox.setMaxWidth(Double.MAX_VALUE);
 767                     comboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
 768                         @Override
 769                         public void changed(ObservableValue ov, Object oldValue, Object newValue) {
 770                             if (isEditing()) {
 771                                 commitEdit(newValue);
 772                             }
 773                         }
 774                     });
 775                 }
 776             }
 777         }
 778 
 779         private class EditingChoiceBoxCell extends TableCell {
 780 
 781             ObservableList items;
 782             ChoiceBox choiceBox;
 783 
 784             public EditingChoiceBoxCell(ObservableList _items) {
 785                 items = _items;
 786                 setId(EDITING_CHOICEBOX_CELL_ID);
 787             }
 788 
 789             @Override
 790             public void startEdit() {
 791                 if (isEmpty()) {
 792                     return;
 793                 }
 794 
 795                 createComboBox();
 796 
 797                 choiceBox.getSelectionModel().select(getItem());
 798 
 799                 super.startEdit();
 800                 setText(null);
 801                 setGraphic(choiceBox);
 802             }
 803 
 804             @Override
 805             public void cancelEdit() {
 806                 super.cancelEdit();
 807                 setGraphic(null);
 808                 setText(getString());
 809             }
 810 
 811             @Override
 812             public void updateItem(Object item, boolean isEmpty) {
 813                 super.updateItem(item, isEmpty);
 814                 if (isEmpty()) {
 815                     setText(null);
 816                     setGraphic(null);
 817                 } else {
 818                     if (isEditing()) {
 819                         if (choiceBox != null) {
 820                             choiceBox.getSelectionModel().select(getItem());
 821                         }
 822                         setText(null);
 823                         setGraphic(choiceBox);
 824                     } else {
 825                         setText(getString());
 826                         setGraphic(null);
 827                     }
 828                 }
 829             }
 830 
 831             private String getString() {
 832                 return getItem() == null ? "" : getItem().toString();
 833             }
 834 
 835             private void createComboBox() {
 836                 if (null == choiceBox) {
 837                     choiceBox = new ChoiceBox(items);
 838                     choiceBox.setMaxWidth(Double.MAX_VALUE);
 839                     choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
 840                         @Override
 841                         public void changed(ObservableValue ov, Object oldValue, Object newValue) {
 842                             if (isEditing()) {
 843                                 commitEdit(newValue);
 844                             }
 845                         }
 846                     });
 847                 }
 848             }
 849         }
 850     }
 851 
 852     /**
 853      * Produces 2D array of strings to facilitate multiple columns sorting
 854      * tests.
 855      *
 856      * @param COLS number of columns in the table
 857      * @return 2D array of strings
 858      */
 859     public static String[][] getDataForSorting(final int COLS) {
 860         int rows;
 861         if (COLS < 32) {
 862             rows = 1 << COLS;
 863         } else {
 864             rows = (int) Math.pow(2, COLS);
 865         }
 866 
 867         char alphabet[] = new char[52];
 868         for (int i = 0; i < 26; i++) {
 869             alphabet[i] = (char) ('A' + i);
 870         }
 871         for (int i = 0; i < 26; i++) {
 872             alphabet[i + 26] = (char) ('a' + i);
 873         }
 874 
 875         String[][] testData = new String[rows][];
 876         for (int x = 0; x < rows; x++) {
 877 
 878             testData[x] = new String[COLS];
 879 
 880             for (int y = 0; y < COLS; y++) {
 881                 int pos = x / (rows >> y + 1);
 882                 if (pos >= 52) {
 883 
 884                     StringBuilder sb = new StringBuilder();
 885 
 886                     int quotient = pos / 52;
 887                     while (quotient-- > 0) {
 888                         sb.append("z");
 889                     }
 890 
 891                     sb.append(alphabet[pos % 52]);
 892                     testData[x][y] = sb.toString();
 893 
 894                 } else {
 895                     testData[x][y] = String.valueOf(alphabet[pos]);
 896                 }
 897             }
 898         }
 899         return testData;
 900     }
 901 }