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 }