package com.netthreads.network.osc.router.controller; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.LinkedList; import java.util.ResourceBundle; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.SplitPane; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.stage.Window; import javafx.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netthreads.network.osc.router.AppInjector; import com.netthreads.network.osc.router.definition.ApplicationMessages; import com.netthreads.network.osc.router.definition.AssertHelper; import com.netthreads.network.osc.router.model.OSCItem; import com.netthreads.network.osc.router.model.OSCValue; import com.netthreads.network.osc.router.properties.ApplicationProperties; import com.netthreads.network.osc.router.service.MIDIDeviceCache; import com.netthreads.network.osc.router.service.OSCService; import com.netthreads.network.osc.router.table.cell.DeviceStatusTableCell; import com.netthreads.network.osc.router.table.cell.DeviceTableCell; import com.netthreads.network.osc.router.table.cell.LabelTableCell; import com.netthreads.network.osc.router.table.cell.RouteTableCell; import com.netthreads.network.osc.router.table.cell.WorkingStatusTableCell; /** * OSCRouter UI controller. * */ public class OSCRouterFXController implements Initializable, ImplementsRefresh { private Logger logger = LoggerFactory.getLogger(OSCRouterFXController.class); private static final String IMAGE_RUN = "/control_play_blue.png"; private static final String IMAGE_PAUSE = "/control_pause.png"; private static final String IMAGE_OPEN = "/document_open.png"; private static final String IMAGE_SAVE = "/document_save.png"; @FXML private TableView<OSCItem> dataTable; @FXML private TableView<OSCValue> valueTable; @FXML private TextField portTextField; @FXML private Button openButton; @FXML private Button saveButton; @FXML private Button activateButton; @FXML private SplitPane mainSplitPane; private FileChooser fileChooser; // Model list private LinkedList<OSCItem> list; private ObservableList<OSCItem> messageList; private ObservableList<OSCValue> messageValuesList; private OSCService oscService; private MIDIDeviceCache midiDeviceCache; private Stage stage; private ApplicationProperties applicationProperties; private ImageView[] activateButtonStates; private ImageView[] openButtonStates; private ImageView[] saveButtonStates; /** * Construct controller. * */ public OSCRouterFXController() { fileChooser = new FileChooser(); String workingDir = workingDir(); if (workingDir != null && !workingDir.isEmpty()) { fileChooser.setInitialDirectory(new File(workingDir)); } // Create observable list. list = new LinkedList<OSCItem>(); messageList = FXCollections.synchronizedObservableList(FXCollections.observableList(list)); messageValuesList = FXCollections.synchronizedObservableList(FXCollections.observableList(new LinkedList<OSCValue>())); applicationProperties = AppInjector.getInjector().getInstance(ApplicationProperties.class); midiDeviceCache = AppInjector.getInjector().getInstance(MIDIDeviceCache.class); openButtonStates = new ImageView[2]; saveButtonStates = new ImageView[2]; activateButtonStates = new ImageView[2]; } /** * Initialise controller. * */ @Override public void initialize(URL url, ResourceBundle rsrcs) { logger.debug("initialize"); assert dataTable != null : AssertHelper.fxmlInsertionError("dataTable"); assert portTextField != null : AssertHelper.fxmlInsertionError("portTextField"); assert openButton != null : AssertHelper.fxmlInsertionError("openButton"); assert saveButton != null : AssertHelper.fxmlInsertionError("saveButton"); assert activateButton != null : AssertHelper.fxmlInsertionError("activateButton"); assert mainSplitPane != null : AssertHelper.fxmlInsertionError("mainSplitPane"); mainSplitPane.setDividerPosition(0, 0.6); // Assemble Tables buildDataTable(dataTable); buildValueTable(valueTable); // --------------------------------------------------------------- // Activate button. // --------------------------------------------------------------- // Go button graphic(s) InputStream stream = getClass().getResourceAsStream(IMAGE_RUN); Image image = new Image(stream); ImageView imageView = new ImageView(image); activateButtonStates[0] = imageView; stream = getClass().getResourceAsStream(IMAGE_PAUSE); image = new Image(stream); imageView = new ImageView(image); activateButtonStates[1] = imageView; activateButton.setGraphic(activateButtonStates[0]); // --------------------------------------------------------------- // Open button. // --------------------------------------------------------------- stream = getClass().getResourceAsStream(IMAGE_OPEN); image = new Image(stream); imageView = new ImageView(image); openButtonStates[0] = imageView; openButton.setGraphic(openButtonStates[0]); // --------------------------------------------------------------- // Save button. // --------------------------------------------------------------- stream = getClass().getResourceAsStream(IMAGE_SAVE); image = new Image(stream); imageView = new ImageView(image); saveButtonStates[0] = imageView; saveButton.setGraphic(saveButtonStates[0]); // Set UI String portValue = String.valueOf(applicationProperties.getPort()); portTextField.setText(portValue); } /** * Load sample file on startup. * */ private void loadSampleFile() { if (applicationProperties.isLoadSampleFile()) { try { oscService.load(ApplicationProperties.SAMPLE_SETTINGS); refresh(); } catch (Exception e) { logger.error("Cannot load sample file"); } } } /** * Activate Button Action Handler. * * @param event */ public void activateButtonAction(ActionEvent event) { logger.debug("activateButtonAction"); String portValue = portTextField.getText(); if (portValue == null || portValue.isEmpty()) { // Alert Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_PORT); alert.showAndWait(); } else { try { int port = Integer.valueOf(portValue); // Perform operation and alter icon depending on active state. if (oscService.getActive()) { // --------------------------------------------------- // Stop OSC Service // --------------------------------------------------- oscService.stop(); activateButton.setGraphic(activateButtonStates[0]); } else { activateButton.setGraphic(activateButtonStates[1]); // --------------------------------------------------- // Start OSC Service // --------------------------------------------------- // Using this port. oscService.setPort(port); // Start service. oscService.run(); } } catch (NumberFormatException e) { Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_PORT); alert.showAndWait(); } } } /** * Open Button Action Handler. * * @param event */ public void openButtonAction(ActionEvent event) { logger.debug("openButtonAction"); Window window = getWindow(openButton); if (window != null) { File directory = fileChooser.showOpenDialog(window); if (directory != null) { try { oscService.load(directory.getAbsolutePath()); refresh(); } catch (Exception exception) { // Alert Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_FILE_LOAD); alert.showAndWait(); } } } } /** * Open Button Action Handler. * * @param event */ public void saveButtonAction(ActionEvent event) { logger.debug("saveButtonAction"); Window window = getWindow(saveButton); if (window != null) { File directory = fileChooser.showSaveDialog(window); if (directory != null) { try { oscService.save(directory.getAbsolutePath()); refresh(); } catch (Exception exception) { // Alert Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_FILE_LOAD); alert.showAndWait(); } } } } /** * Build message table columns. * * @param dataTable */ @SuppressWarnings("unchecked") private void buildDataTable(TableView<OSCItem> dataTable) { dataTable.setEditable(true); // --------------------------------------------------------------- // Build columns. // --------------------------------------------------------------- // --------------------------------------------------------------- // Address // --------------------------------------------------------------- TableColumn<OSCItem, String> addressCol = new TableColumn<OSCItem, String>(OSCItem.TITLE_ADDRESS); addressCol.setCellValueFactory(new PropertyValueFactory<OSCItem, String>(OSCItem.ATTR_ADDRESS)); // --------------------------------------------------------------- // Route indicator // --------------------------------------------------------------- TableColumn<OSCItem, String> routeCol = new TableColumn<OSCItem, String>(OSCItem.TITLE_ROUTE); routeCol.setCellValueFactory(new PropertyValueFactory<OSCItem, String>(OSCItem.ATTR_ROUTE)); // Custom Cell factory converts index to image. routeCol.setCellFactory(new Callback<TableColumn<OSCItem, String>, TableCell<OSCItem, String>>() { @Override public TableCell<OSCItem, String> call(TableColumn<OSCItem, String> item) { RouteTableCell cell = new RouteTableCell(); // Can't edit when the service is running. cell.editableProperty().bind(oscService.runningProperty().not()); return cell; } }); routeCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<OSCItem, String>>() { @Override public void handle(TableColumn.CellEditEvent<OSCItem, String> t) { t.getRowValue().setRoute(t.getNewValue()); } }); // --------------------------------------------------------------- // Device Choice // --------------------------------------------------------------- final String[] deviceNames = midiDeviceCache.getNames(); TableColumn<OSCItem, String> deviceCol = new TableColumn<OSCItem, String>(OSCItem.TITLE_DEVICE); deviceCol.setCellValueFactory(new PropertyValueFactory<OSCItem, String>(OSCItem.ATTR_DEVICE)); // Custom Cell factory converts index to image. deviceCol.setCellFactory(new Callback<TableColumn<OSCItem, String>, TableCell<OSCItem, String>>() { @Override public TableCell<OSCItem, String> call(TableColumn<OSCItem, String> item) { DeviceTableCell cell = new DeviceTableCell(deviceNames); // Can't edit when the service is running. cell.editableProperty().bind(oscService.runningProperty().not()); return cell; } }); deviceCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<OSCItem, String>>() { @Override public void handle(TableColumn.CellEditEvent<OSCItem, String> t) { // Deselect old device. String oldName = t.getOldValue(); midiDeviceCache.select(oldName, false); // Select new device. String newName = t.getNewValue(); t.getRowValue().setDevice(newName); midiDeviceCache.select(newName, true); } }); // --------------------------------------------------------------- // Device Status // --------------------------------------------------------------- TableColumn<OSCItem, Integer> deviceStatusCol = new TableColumn<OSCItem, Integer>(OSCItem.TITLE_STATUS); deviceStatusCol.setCellValueFactory(new PropertyValueFactory<OSCItem, Integer>(OSCItem.ATTR_STATUS)); // Custom Cell factory converts index to image. deviceStatusCol.setCellFactory(new Callback<TableColumn<OSCItem, Integer>, TableCell<OSCItem, Integer>>() { @Override public TableCell<OSCItem, Integer> call(TableColumn<OSCItem, Integer> item) { DeviceStatusTableCell cell = new DeviceStatusTableCell(); return cell; } }); // --------------------------------------------------------------- // Working indicator // --------------------------------------------------------------- TableColumn<OSCItem, Integer> workingCol = new TableColumn<OSCItem, Integer>(OSCItem.TITLE_WORKING); workingCol.setCellValueFactory(new PropertyValueFactory<OSCItem, Integer>(OSCItem.ATTR_WORKING)); // Custom Cell factory converts index to image. workingCol.setCellFactory(new Callback<TableColumn<OSCItem, Integer>, TableCell<OSCItem, Integer>>() { @Override public TableCell<OSCItem, Integer> call(TableColumn<OSCItem, Integer> item) { WorkingStatusTableCell cell = new WorkingStatusTableCell(); return cell; } }); // --------------------------------------------------------------- // Set widths and bind to data table width. // --------------------------------------------------------------- addressCol.prefWidthProperty().bind(dataTable.widthProperty().divide(4)); routeCol.prefWidthProperty().bind(dataTable.widthProperty().divide(6)); deviceCol.prefWidthProperty().bind(dataTable.widthProperty().divide(5)); deviceStatusCol.prefWidthProperty().bind(dataTable.widthProperty().divide(5)); workingCol.prefWidthProperty().bind(dataTable.widthProperty().divide(5)); // --------------------------------------------------------------- // Add columns. // --------------------------------------------------------------- dataTable.getColumns().setAll(addressCol, routeCol, deviceCol, deviceStatusCol, workingCol); // --------------------------------------------------------------- // Selection handler. // --------------------------------------------------------------- dataTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<OSCItem>() { public void changed(javafx.beans.value.ObservableValue<? extends OSCItem> ov, OSCItem oldValue, OSCItem newValue) { messageValuesList.clear(); messageValuesList.addAll(newValue.getValues()); valueTable.setItems(messageValuesList); }; }); // --------------------------------------------------------------- // Assign list // --------------------------------------------------------------- dataTable.setItems(messageList); } /** * Build values table columns. * * @param dataTable */ @SuppressWarnings("unchecked") private void buildValueTable(TableView<OSCValue> valueTable) { valueTable.setEditable(true); // --------------------------------------------------------------- // --------------------------------------------------------------- // Build columns. // --------------------------------------------------------------- // --------------------------------------------------------------- // --------------------------------------------------------------- // Argument // --------------------------------------------------------------- TableColumn<OSCValue, String> typeCol = new TableColumn<OSCValue, String>(OSCValue.TITLE_TYPE); typeCol.setCellValueFactory(new PropertyValueFactory<OSCValue, String>(OSCValue.ATTR_TYPE)); // --------------------------------------------------------------- // Value // --------------------------------------------------------------- TableColumn<OSCValue, String> valueCol = new TableColumn<OSCValue, String>(OSCValue.TITLE_VALUE); valueCol.setCellValueFactory(new PropertyValueFactory<OSCValue, String>(OSCValue.ATTR_VALUE)); // --------------------------------------------------------------- // Labels // --------------------------------------------------------------- TableColumn<OSCValue, String> labelCol = new TableColumn<OSCValue, String>(OSCValue.TITLE_LABEL); labelCol.setCellValueFactory(new PropertyValueFactory<OSCValue, String>(OSCValue.ATTR_LABEL)); // Add Label editor labelCol.setCellFactory(new Callback<TableColumn<OSCValue, String>, TableCell<OSCValue, String>>() { @Override public TableCell<OSCValue, String> call(TableColumn<OSCValue, String> p) { LabelTableCell<OSCValue, String> cell = new LabelTableCell<OSCValue, String>(); return cell; } }); // Add label commit change handler. labelCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<OSCValue, String>>() { @Override public void handle(TableColumn.CellEditEvent<OSCValue, String> t) { t.getRowValue().setLabel(t.getNewValue()); } }); // --------------------------------------------------------------- // --------------------------------------------------------------- // Set widths and bind to data table width. // --------------------------------------------------------------- // --------------------------------------------------------------- typeCol.prefWidthProperty().bind(dataTable.widthProperty().divide(5)); valueCol.prefWidthProperty().bind(dataTable.widthProperty().divide(5)); labelCol.prefWidthProperty().bind(dataTable.widthProperty().divide(5)); // --------------------------------------------------------------- // --------------------------------------------------------------- // Add columns. // --------------------------------------------------------------- // --------------------------------------------------------------- valueTable.getColumns().setAll(typeCol, valueCol, labelCol); } /** * Controller client act as an intermediary between the workers and the UI * controller. * * @param oscService */ public void setService(OSCService oscService) { this.oscService = oscService; if (oscService != null) { loadSampleFile(); } } /** * Return list object. * * @return The list object. */ public ObservableList<OSCItem> getObservableList() { return messageList; } /** * Assign stage. * * @param stage */ public void setStage(Stage stage) { this.stage = stage; } /** * Get window from node. * * @param node * * @return The node Window. */ private Window getWindow(Node node) { Window window = null; Scene scene = node.getScene(); if (scene != null) { window = scene.getWindow(); } return window; } /** * Trick to force data table refresh. * */ @Override public void refresh() { Platform.runLater(new Runnable() { /** * Run later. Note we can't hammer the UI with refresh requests all * the time as it caused the UI interaction to become sluggish. * */ public void run() { refreshDataTable(dataTable); refreshValueTable(valueTable); } /** * Refresh data table. * * @param tableView */ private void refreshDataTable(TableView<OSCItem> tableView) { ObservableList<TableColumn<OSCItem, ?>> columns = tableView.getColumns(); TableColumn<OSCItem, ?> column = columns.get(0); if (column != null) { column.setVisible(false); column.setVisible(true); } } /** * Refresh values table. * * @param tableView */ private void refreshValueTable(TableView<OSCValue> tableView) { ObservableList<TableColumn<OSCValue, ?>> columns = tableView.getColumns(); TableColumn<OSCValue, ?> column = columns.get(0); if (column != null) { column.setVisible(false); column.setVisible(true); } } }); } /** * Attempt to get working directory. * * @return The current working directory. */ private String workingDir() { String currentDir = System.getProperty("user.dir"); if (currentDir == null || currentDir.isEmpty()) { try { currentDir = new java.io.File(".").getCanonicalPath(); } catch (IOException e) { logger.error("Can't obtain working directory"); } } return currentDir; } }