/***********************************************************************************
*
* Copyright (c) 2014 Kamil Baczkowicz
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*
* Kamil Baczkowicz - initial API and implementation and/or initial documentation
*
*/
package pl.baczkowicz.mqttspy.ui;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.baczkowicz.mqttspy.common.generated.ProtocolVersionEnum;
import pl.baczkowicz.mqttspy.configuration.ConfigurationManager;
import pl.baczkowicz.mqttspy.configuration.ConfiguredConnectionDetails;
import pl.baczkowicz.mqttspy.configuration.generated.UserInterfaceMqttConnectionDetails;
import pl.baczkowicz.mqttspy.ui.connections.ConnectionManager;
import pl.baczkowicz.mqttspy.ui.events.ConnectionStatusChangeEvent;
import pl.baczkowicz.mqttspy.utils.ConnectionUtils;
import pl.baczkowicz.mqttspy.utils.MqttUtils;
import pl.baczkowicz.spy.common.generated.ConnectionGroup;
import pl.baczkowicz.spy.common.generated.ConnectionGroupReference;
import pl.baczkowicz.spy.common.generated.ConnectionReference;
import pl.baczkowicz.spy.configuration.BaseConfigurationUtils;
import pl.baczkowicz.spy.eventbus.IKBus;
import pl.baczkowicz.spy.exceptions.ConfigurationException;
import pl.baczkowicz.spy.ui.configuration.ConfiguredConnectionGroupDetails;
import pl.baczkowicz.spy.ui.controls.DragAndDropTreeViewCell;
import pl.baczkowicz.spy.ui.events.observers.ItemsReorderedObserver;
import pl.baczkowicz.spy.ui.panes.SpyPerspective;
import pl.baczkowicz.spy.ui.properties.ConnectionTreeItemProperties;
import pl.baczkowicz.spy.ui.threading.SimpleRunLaterExecutor;
import pl.baczkowicz.spy.ui.utils.DialogFactory;
import pl.baczkowicz.spy.ui.utils.TooltipFactory;
/**
* Controller for editing all connections.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class EditConnectionsController extends AnchorPane implements Initializable, ItemsReorderedObserver
{
/** Diagnostic logger. */
private final static Logger logger = LoggerFactory.getLogger(EditConnectionsController.class);
/**
* The name of this field needs to be set to the name of the pane +
* Controller (i.e. <fx:id>Controller).
*/
@FXML
private EditConnectionController editConnectionPaneController;
/**
* The name of this field needs to be set to the name of the pane +
* Controller (i.e. <fx:id>Controller).
*/
@FXML
private EditConnectionGroupController editConnectionGroupPaneController;
@FXML
private AnchorPane connectionDetailsPane;
@FXML
private TreeView<ConnectionTreeItemProperties> connectionList;
@FXML
private Button duplicateConnectionButton;
@FXML
private Button deleteConnectionButton;
@FXML
private Button importConnectionsButton;
@FXML
private Button applyAllButton;
@FXML
private Button undoAllButton;
@FXML
private Label changesDetectedLabel;
private MainController mainController;
private ConfigurationManager configurationManager;
private List<ConfiguredConnectionDetails> connections = new ArrayList<ConfiguredConnectionDetails>();
// private EventManager eventManager;
private IKBus eventBus;
private ConnectionManager connectionManager;
int lastUsedId = 0;
final ConnectionTreeItemProperties rootItemProperties = new ConnectionTreeItemProperties(lastUsedId++);
final TreeItem<ConnectionTreeItemProperties> rootItem = new TreeItem<ConnectionTreeItemProperties>(rootItemProperties);
private List<ConfiguredConnectionGroupDetails> groups;
@FXML
private Node editConnectionPane;
@FXML
private Node editConnectionGroupPane;
// ===============================
// === Initialisation ============
// ===============================
public void initialize(URL location, ResourceBundle resources)
{
final EditConnectionsController controller = this;
connectionList.setCellFactory(new Callback<TreeView<ConnectionTreeItemProperties>, TreeCell<ConnectionTreeItemProperties>>()
{
@Override
public TreeCell call(TreeView<ConnectionTreeItemProperties> treeView)
{
return new DragAndDropTreeViewCell(treeView, controller);
}
});
duplicateConnectionButton.setDisable(true);
deleteConnectionButton.setDisable(true);
connectionList.setShowRoot(true);
connectionList.setRoot(rootItem);
rootItem.setExpanded(true);
rootItem.expandedProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue)
{
if (!rootItem.isExpanded())
{
rootItem.setExpanded(true);
}
}
});
connectionList.getStyleClass().add("connectionList");
connectionList.getSelectionModel().selectedItemProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
logger.debug("Item selected = " + newValue);
if (newValue == null)
{
return;
}
// showSelected();
updateUIForSelectedItem(((TreeItem<ConnectionTreeItemProperties>) newValue).getValue());
}
});
connectionList.setOnMouseClicked(new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent mouseEvent)
{
if (mouseEvent.getButton().equals(MouseButton.PRIMARY))
{
if (mouseEvent.getClickCount() == 2)
{
try
{
if (getSelectedItem() != null && !getSelectedItem().isGroup())
{
// Open the connection
editConnectionPaneController.createConnection();
}
}
catch (ConfigurationException e)
{
logger.error("Cannot create connection", e);
}
}
}
}
});
}
public void init()
{
connections = configurationManager.getConnections();
groups = configurationManager.getConnectionGrops();
rootItemProperties.setGroup(configurationManager.getRootGroup());
eventBus.subscribe(this, this::onConnectionStatusChanged, ConnectionStatusChangeEvent.class, new SimpleRunLaterExecutor());
// eventManager.registerConnectionStatusObserver(this, null);
editConnectionGroupPaneController.setMainController(mainController);
editConnectionGroupPaneController.setEditConnectionsController(this);
editConnectionGroupPaneController.init();
editConnectionPaneController.setConfigurationManager(configurationManager);
editConnectionPaneController.setEventBus(eventBus);
editConnectionPaneController.setConnectionManager(connectionManager);
editConnectionPaneController.setMainController(mainController);
editConnectionPaneController.setEditConnectionsController(this);
editConnectionPaneController.init();
editConnectionPaneController.getConnectionName().textProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observable,
Object oldValue, Object newValue)
{
if (editConnectionPaneController
.isRecordModifications())
{
connectionNameChanged();
}
}
});
editConnectionPaneController.setRecordModifications(false);
listConnections();
editConnectionPaneController.setRecordModifications(true);
}
/**
* TreeItem contains ConnectionTreeItemProperties
*
* ConnectionTreeItemProperties contains either
* ConfiguredConnectionDetails
* ConfiguredConnectionGroupDetails
*
* ConfiguredConnectionDetails extends the connection object
* ConfiguredConnectionGroupDetails extends the group object
*
*
* @param groups
* @param connectionList
*/
private void populateConnections(
final List<ConfiguredConnectionGroupDetails> groups,
final List<ConfiguredConnectionDetails> connectionList)
{
rootItem.getChildren().clear();
rootItemProperties.getChildren().clear();
final List<ConnectionTreeItemProperties> treeItemGroupProperties = new ArrayList<>();
final List<ConnectionTreeItemProperties> treeItemConnectionProperties = new ArrayList<>();
// Builds the tree item properties
buildTree(rootItemProperties, treeItemGroupProperties, treeItemConnectionProperties);
// Adds tree items to the given root
addToTree(rootItem, rootItemProperties);
}
private void buildTree(ConnectionTreeItemProperties treeItem,
final List<ConnectionTreeItemProperties> treeItemGroupProperties,
final List<ConnectionTreeItemProperties> treeItemConnectionProperties)
{
// This is always called for a group
final ConfiguredConnectionGroupDetails group = (ConfiguredConnectionGroupDetails) treeItem.getGroup();
for (final ConnectionGroupReference reference : group.getSubgroups())
{
final ConfiguredConnectionGroupDetails subgroup = (ConfiguredConnectionGroupDetails) reference.getReference();
// Create new tree item for the subgroup
final ConnectionTreeItemProperties subgroupTreeItemProperties = new ConnectionTreeItemProperties(lastUsedId++);
subgroupTreeItemProperties.setGroup(subgroup);
// Add the tree item subgroup to the parent tree item
treeItemGroupProperties.add(subgroupTreeItemProperties);
// Set the parent/child relationship for the tree items (this is already set on the configuration objects)
subgroupTreeItemProperties.setParent(treeItem);
treeItem.addChild(subgroupTreeItemProperties);
// Recursive
buildTree(subgroupTreeItemProperties, treeItemGroupProperties, treeItemConnectionProperties);
}
for (final ConnectionReference reference : group.getConnections())
{
final ConfiguredConnectionDetails connection = (ConfiguredConnectionDetails) reference.getReference();
// Create new tree item for the connection
final ConnectionTreeItemProperties connectionTreeItemProperties = new ConnectionTreeItemProperties(lastUsedId++);
connectionTreeItemProperties.setConnection(connection);
// Add the tree item connection to the parent tree item
treeItemConnectionProperties.add(connectionTreeItemProperties);
// Set the parent/child relationship for the tree items (this is already set on the configuration objects)
connectionTreeItemProperties.setParent(treeItem);
treeItem.addChild(connectionTreeItemProperties);
}
}
private boolean addToTree(TreeItem<ConnectionTreeItemProperties> treeItem, final ConnectionTreeItemProperties properties)
{
boolean added = false;
for (final ConnectionTreeItemProperties item : properties.getChildren())
{
final TreeItem<ConnectionTreeItemProperties> newTreeItem = new TreeItem<ConnectionTreeItemProperties>(item);
if (item.isGroup())
{
if (addToTree(newTreeItem, item))
{
newTreeItem.setExpanded(true);
}
}
treeItem.getChildren().add(newTreeItem);
added = true;
}
return added;
}
// private void showSelected()
// {
// synchronized (connections)
// {
// updateUIForSelectedItem();
// }
// }
public void updateUIForSelectedItem()
{
updateUIForSelectedItem(getSelectedItem());
}
public void updateUIForSelectedItem(final ConnectionTreeItemProperties selected)
{
if (selected == null)
{
logger.debug("Selection is null");
selectFirst();
return;
}
if (connections.isEmpty() && groups.isEmpty())
{
duplicateConnectionButton.setDisable(true);
deleteConnectionButton.setDisable(true);
editConnectionPaneController.setEmptyConnectionListMode(true);
}
else
{
// This this is not the default group (first one on the list)
if (!selected.isGroup() || !selected.getGroup().getID().equals(BaseConfigurationUtils.DEFAULT_GROUP))
{
deleteConnectionButton.setDisable(false);
}
else
{
deleteConnectionButton.setDisable(true);
}
duplicateConnectionButton.setDisable(selected.isGroup());
editConnectionPaneController.setEmptyConnectionListMode(false);
editConnectionPane.setVisible(!selected.isGroup());
editConnectionGroupPane.setVisible(selected.isGroup());
if (selected.isGroup())
{
editConnectionGroupPaneController.setRecordModifications(false);
editConnectionGroupPaneController.editConnectionGroup((ConfiguredConnectionGroupDetails) selected.getGroup(), selected.getChildren());
editConnectionGroupPaneController.setRecordModifications(true);
}
else if (!((ConfiguredConnectionDetails) selected.getConnection()).isBeingCreated())
{
logger.trace("Editing connection {}", selected.getName());
editConnectionPaneController.setRecordModifications(false);
editConnectionPaneController.editConnection((ConfiguredConnectionDetails) selected.getConnection());
editConnectionPaneController.setRecordModifications(true);
}
}
}
/**
* This links the group with its parent group - done on the configuration objects.
*
* @param groupDetails
* @param parent
*/
private void addToParentGroup(final ConfiguredConnectionGroupDetails groupDetails, final ConfiguredConnectionGroupDetails parent)
{
groupDetails.setGroup(new ConnectionGroupReference(parent));
parent.getSubgroups().add(new ConnectionGroupReference(groupDetails));
}
/**
* This links the connection with its parent group - done on the configuration objects.
*
* @param groupDetails
* @param parent
*/
private void addToParentGroup(final ConfiguredConnectionDetails connectionDetails, final ConfiguredConnectionGroupDetails parent)
{
connectionDetails.setGroup(new ConnectionGroupReference(parent));
parent.getConnections().add(new ConnectionReference(connectionDetails));
}
// ===============================
// === FXML ======================
// ===============================
@FXML
public void newMqttConnection()
{
final UserInterfaceMqttConnectionDetails baseConnection = new UserInterfaceMqttConnectionDetails();
baseConnection.getServerURI().add("127.0.0.1");
baseConnection.setClientID(MqttUtils.generateClientIdWithTimestamp(System.getProperty("user.name"), ProtocolVersionEnum.MQTT_DEFAULT));
baseConnection.setName(ConnectionUtils.composeConnectionName(baseConnection.getClientID(), baseConnection.getServerURI()));
baseConnection.setAutoConnect(true);
final ConfiguredConnectionDetails connectionDetails = new ConfiguredConnectionDetails(
true, true, baseConnection);
connectionDetails.setID(ConfigurationManager.generateConnectionId());
addToParentGroup(connectionDetails, configurationManager.getRootGroup());
connections.add(connectionDetails);
newConnectionMode(connectionDetails);
}
@FXML
private void duplicateConnection()
{
final ConnectionGroupReference parent = ((ConfiguredConnectionDetails) getSelectedItem().getConnection()).getGroup();
((ConfiguredConnectionDetails) getSelectedItem().getConnection()).setGroup(null);
final ConfiguredConnectionDetails connectionDetails = new ConfiguredConnectionDetails(
true, true, (UserInterfaceMqttConnectionDetails) getSelectedItem().getConnection());
connectionDetails.setID(ConfigurationManager.generateConnectionId());
((ConfiguredConnectionDetails) getSelectedItem().getConnection()).setGroup(parent);
addToParentGroup(connectionDetails, (ConfiguredConnectionGroupDetails) parent.getReference());
connections.add(connectionDetails);
newConnectionMode(connectionDetails);
}
@FXML
private void newGroup()
{
try
{
final Optional<String> result = DialogFactory.createInputDialog(
connectionList.getScene().getWindow(), "New connection group", "Please enter the connection group name: ");
if (result.isPresent())
{
final ConnectionGroup group = new ConnectionGroup(ConfigurationManager.generateConnectionGroupId(),
result.get(), new ArrayList(), new ArrayList());
final ConfiguredConnectionGroupDetails groupDetails = new ConfiguredConnectionGroupDetails(group, true);
addToParentGroup(groupDetails, configurationManager.getRootGroup());
groups.add(groupDetails);
//populateConnections(groups, connections);
listConnections();
selectGroup(groupDetails);
}
}
catch (Exception e)
{
logger.error("Cannot create a new group", e);
}
}
@FXML
private void deleteConnection()
{
final ConnectionTreeItemProperties selected = getSelectedItem();
if (getSelectedItem().isGroup())
{
final ConfiguredConnectionGroupDetails group = (ConfiguredConnectionGroupDetails) getSelectedItem().getGroup();
final String groupName = group.getName();
final int childrenCount = selected.getChildren().size();
if (DialogFactory.createQuestionDialog("Deleting connection group",
"Are you sure you want to delete connection group '" + groupName + "'"
+ (childrenCount == 0 ? "" : " and all subitems")
+ "?" + " This cannot be undone.", false).get() == ButtonType.YES)
{
group.removeFromGroup();
groups.remove(group);
listConnections();
selectFirst();
logger.debug("Saving all connections");
if (configurationManager.saveConfiguration())
{
// TODO: for some reason, this is not shown
TooltipFactory.createTooltip(deleteConnectionButton, "Connection group " + groupName + " deleted.");
}
}
}
else
{
final ConfiguredConnectionDetails connection = (ConfiguredConnectionDetails) getSelectedItem().getConnection();
connection.setDeleted(true);
final String connectionName = connection.getName();
if (DialogFactory.createQuestionDialog(
"Deleting connection",
"Are you sure you want to delete connection '" + connectionName + "'?" + " This cannot be undone.",
false).get() == ButtonType.YES)
{
editConnectionPaneController.setRecordModifications(false);
connection.removeFromGroup();
connections.remove(connection);
listConnections();
selectFirst();
editConnectionPaneController.setRecordModifications(true);
logger.debug("Saving all connections");
if (configurationManager.saveConfiguration())
{
// TODO: for some reason, this is not shown
TooltipFactory.createTooltip(deleteConnectionButton, "Connection " + connectionName + " deleted.");
}
}
}
}
@FXML
private void undoAll()
{
// TODO: how to restore all parent-child relationships?
final List<ConfiguredConnectionDetails> allConnections = new ArrayList<>();
allConnections.addAll(connections);
for (final ConfiguredConnectionDetails connection : allConnections)
{
if (connection.isNew())
{
connection.removeFromGroup();
connections.remove(connection);
}
else
{
connection.undoAll();
}
}
final List<ConfiguredConnectionGroupDetails> allGroups = new ArrayList<>();
allGroups.addAll(groups);
for (final ConfiguredConnectionGroupDetails group : allGroups)
{
if (group.isNew())
{
group.removeFromGroup();
groups.remove(group);
}
else
{
group.undoAll();
}
}
listConnections();
}
@FXML
private void applyAll()
{
if (configurationManager.isConfigurationWritable())
{
for (final ConfiguredConnectionDetails connection : connections)
{
connection.apply();
}
for (final ConfiguredConnectionGroupDetails group : groups)
{
group.apply();
}
listConnections();
logger.debug("Saving all connections & groups");
if (configurationManager.saveConfiguration())
{
TooltipFactory.createTooltip(applyAllButton, "Changes for all connections and groups have been saved.");
}
else
{
DialogFactory.createErrorDialog(
"Cannot save the configuration file",
"Oops... an error has occurred while trying to save your configuration. "
+ "Please check the log file for more information. Your changes were not saved.");
}
}
else
{
DialogFactory.createErrorDialog(
"Cannot save the configuration file",
"Oops... your configuration file isn't right. Please restore default configuration. ");
}
}
@FXML
private void importConnections()
{
// TODO: import
}
// ===============================
// === Logic =====================
// ===============================
private void selectFirst()
{
logger.info("Selecting first item...");
// Select the first item if any connections present
if (connections.size() > 0)
{
connectionList.getSelectionModel().select(0);
}
}
public void selectConnection(final ConfiguredConnectionDetails connection)
{
selectConnection(rootItem, connection);
}
public void selectGroup(final ConfiguredConnectionGroupDetails group)
{
selectGroup(rootItem, group);
}
public void selectConnection(final TreeItem<ConnectionTreeItemProperties> parentItem, final ConfiguredConnectionDetails connection)
{
for (final TreeItem<ConnectionTreeItemProperties> treeItem : parentItem.getChildren())
{
final ConnectionTreeItemProperties item = treeItem.getValue();
if (!item.isGroup() && item.getConnection().equals(connection))
{
connectionList.getSelectionModel().select(treeItem);
updateUIForSelectedItem(item);
return;
}
selectConnection(treeItem, connection);
}
}
public void selectGroup(final TreeItem<ConnectionTreeItemProperties> parentItem, final ConfiguredConnectionGroupDetails group)
{
for (final TreeItem<ConnectionTreeItemProperties> treeItem : parentItem.getChildren())
{
final ConnectionTreeItemProperties item = treeItem.getValue();
if (item.isGroup() && item.getGroup().equals(group))
{
connectionList.getSelectionModel().select(treeItem);
updateUIForSelectedItem(item);
return;
}
selectGroup(treeItem, group);
}
}
private ConnectionTreeItemProperties getSelectedItem()
{
if (connectionList.getSelectionModel().getSelectedItem() == null)
{
return null;
}
return connectionList.getSelectionModel().getSelectedItem().getValue();
}
public void listConnections()
{
final TreeItem<ConnectionTreeItemProperties> selectedItem = connectionList.getSelectionModel().getSelectedItem();
ConnectionTreeItemProperties selected = null;
if (selectedItem != null)
{
selected = selectedItem.getValue();
}
applyAllButton.setDisable(true);
undoAllButton.setDisable(true);
setChangesInfoLabelVisibility(false);
populateConnections(groups, connections);
for (int i = 0; i < connections.size(); i++)
{
if (connections.get(i).isModified() || connections.get(i).isGroupingModified())
{
applyAllButton.setDisable(false);
undoAllButton.setDisable(false);
setChangesInfoLabelVisibility(true);
break;
}
}
for (int i = 0; i < groups.size(); i++)
{
if (groups.get(i).isModified() || groups.get(i).isGroupingModified())
{
applyAllButton.setDisable(false);
undoAllButton.setDisable(false);
setChangesInfoLabelVisibility(true);
break;
}
}
// Reselect
if (selected != null)
{
if (selected.isGroup())
{
selectGroup((ConfiguredConnectionGroupDetails) selected.getGroup());
}
else
{
selectConnection((ConfiguredConnectionDetails) selected.getConnection());
}
//connectionList.getSelectionModel().select(selected);
}
else
{
logger.debug("No selection present");
selectFirst();
}
updateUIForSelectedItem();
}
public void setChangesInfoLabelVisibility(final boolean visible)
{
if (visible)
{
AnchorPane.setBottomAnchor(connectionList, 98.0);
}
else
{
AnchorPane.setBottomAnchor(connectionList, 85.0);
}
changesDetectedLabel.setVisible(visible);
}
protected void connectionNameChanged()
{
if (getSelectedItem() != null)
{
final String newName = editConnectionPaneController.getConnectionName().getText();
getSelectedItem().getConnection().setName(newName);
listConnections();
}
}
private void newConnectionMode(final ConfiguredConnectionDetails createdConnection)
{
editConnectionPaneController.setRecordModifications(false);
listConnections();
selectConnection(rootItem, createdConnection);
editConnectionPaneController.editConnection(createdConnection);
editConnectionPaneController.setRecordModifications(true);
}
public void openConnection(final ConfiguredConnectionDetails connectionDetails)
{
editConnectionPaneController.openConnection(connectionDetails);
}
// ===============================
// === Setters and getters =======
// ===============================
public void setMainController(MainController mainController)
{
this.mainController = mainController;
}
public void setConfigurationManager(final ConfigurationManager configurationManager)
{
this.configurationManager = configurationManager;
}
public void onConnectionStatusChanged(final ConnectionStatusChangeEvent event)
{
if (getSelectedItem() != null)
{
//showSelected();
updateUIForSelectedItem();
}
}
public void setConnectionManager(final ConnectionManager connectionManager)
{
this.connectionManager = connectionManager;
}
@Override
public void onItemsReordered()
{
listConnections();
}
public void setPerspective(final SpyPerspective perspective)
{
editConnectionPaneController.setPerspective(perspective);
}
public void setEventBus(final IKBus eventBus)
{
this.eventBus = eventBus;
}
}