/***********************************************************************************
*
* 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.baczkowicz.mqttspy.configuration.ConfigurationManager;
import pl.baczkowicz.mqttspy.configuration.ConfiguredConnectionDetails;
import pl.baczkowicz.mqttspy.connectivity.MqttAsyncConnection;
import pl.baczkowicz.mqttspy.connectivity.MqttConnectionStatus;
import pl.baczkowicz.mqttspy.ui.connections.ConnectionManager;
import pl.baczkowicz.mqttspy.ui.controlpanel.ControlPanelStatsUpdater;
import pl.baczkowicz.mqttspy.ui.controlpanel.GettingInvolvedTooltip;
import pl.baczkowicz.mqttspy.ui.controlpanel.ItemStatus;
import pl.baczkowicz.mqttspy.ui.events.ConfigurationLoadedEvent;
import pl.baczkowicz.mqttspy.ui.events.ConnectionStatusChangeEvent;
import pl.baczkowicz.mqttspy.ui.events.ConnectionsChangedEvent;
import pl.baczkowicz.mqttspy.ui.events.LoadConfigurationFileEvent;
import pl.baczkowicz.mqttspy.ui.events.ShowExternalWebPageEvent;
import pl.baczkowicz.mqttspy.ui.properties.VersionInfoProperties;
import pl.baczkowicz.mqttspy.ui.utils.ActionUtils;
import pl.baczkowicz.mqttspy.ui.utils.DialogUtils;
import pl.baczkowicz.mqttspy.ui.utils.StylingUtils;
import pl.baczkowicz.mqttspy.versions.VersionManager;
import pl.baczkowicz.mqttspy.versions.events.VersionInfoErrorEvent;
import pl.baczkowicz.mqttspy.versions.events.VersionInfoReceivedEvent;
import pl.baczkowicz.mqttspy.versions.generated.MqttSpyVersions;
import pl.baczkowicz.spy.configuration.BasePropertyNames;
import pl.baczkowicz.spy.eventbus.IKBus;
import pl.baczkowicz.spy.exceptions.ConfigurationException;
import pl.baczkowicz.spy.exceptions.XMLException;
import pl.baczkowicz.spy.ui.configuration.ConfiguredConnectionGroupDetails;
import pl.baczkowicz.spy.ui.threading.SimpleRunLaterExecutor;
import pl.baczkowicz.spy.utils.ThreadingUtils;
/**
* The controller looking after the control panel.
*/
public class ControlPanelController extends AnchorPane implements Initializable
{
private final static Logger logger = LoggerFactory.getLogger(ControlPanelController.class);
private static final double MAX_CONNECTIONS_HEIGHT = 350;
/**
* The name of this field needs to be set to the name of the pane +
* Controller (i.e. <fx:id>Controller).
*/
@FXML
private ControlPanelItemController controlPanelItem1Controller;
@FXML
private ControlPanelItemController controlPanelItem2Controller;
@FXML
private ControlPanelItemController controlPanelItem3Controller;
@FXML
private ControlPanelItemController controlPanelItem4Controller;
@FXML
private Button button1;
@FXML
private Button button2;
@FXML
private Button button3;
@FXML
private Button button4;
private VersionManager versionManager;
private ConfigurationManager configurationManager;
private MainController mainController;
// private EventManager<FormattedMqttMessage> eventManager;
private IKBus eventBus;
private ConnectionManager connectionManager;
private ControlPanelStatsUpdater statsUpdater;
private Map<MqttConnectionStatus, String> nextActionTitle = new HashMap<MqttConnectionStatus, String>();
private GettingInvolvedTooltip gettingInvolvedTooltip;
// ===============================
// === Initialisation ============
// ===============================
public void initialize(URL location, ResourceBundle resources)
{
nextActionTitle.put(MqttConnectionStatus.NOT_CONNECTED, "Connect to");
nextActionTitle.put(MqttConnectionStatus.CONNECTING, "Connecting to");
nextActionTitle.put(MqttConnectionStatus.CONNECTED, "Disconnect from");
nextActionTitle.put(MqttConnectionStatus.DISCONNECTED, "Connect to");
nextActionTitle.put(MqttConnectionStatus.DISCONNECTING, "Disconnecting from");
}
public void init()
{
eventBus.subscribe(this, this::onVersionInfoReceived, VersionInfoReceivedEvent.class, new SimpleRunLaterExecutor());
eventBus.subscribe(this, this::onVersionInfoError, VersionInfoErrorEvent.class, new SimpleRunLaterExecutor());
eventBus.subscribe(this, this::onConnectionStatusChanged, ConnectionStatusChangeEvent.class, new SimpleRunLaterExecutor());
eventBus.subscribe(this, this::onConnectionsChanged, ConnectionsChangedEvent.class);
eventBus.subscribe(this, this::onConfigurationFileStatusChange, ConfigurationLoadedEvent.class);
controlPanelItem1Controller.setConfigurationMananger(configurationManager);
controlPanelItem2Controller.setConfigurationMananger(configurationManager);
controlPanelItem3Controller.setConfigurationMananger(configurationManager);
controlPanelItem4Controller.setConfigurationMananger(configurationManager);
// Item 1
showConfigurationFileStatus(controlPanelItem1Controller, button1);
// Item 2
showConnections(controlPanelItem2Controller, button2);
// Item 3
checkForUpdates(controlPanelItem3Controller, button3);
// Item 4
showStats(controlPanelItem4Controller, button4);
}
// ===============================
// === FXML ======================
// ===============================
// ===============================
// === Logic =====================
// ===============================
private void showStats(final ControlPanelItemController controller, final Button button)
{
controlPanelItem4Controller.refresh();
statsUpdater = new ControlPanelStatsUpdater(controlPanelItem4Controller, button, eventBus);
statsUpdater.show();
gettingInvolvedTooltip = new GettingInvolvedTooltip();
button.setTooltip(gettingInvolvedTooltip);
button.setOnMouseMoved(new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
gettingInvolvedTooltip.setCurrentMousePosition(event);
if (gettingInvolvedTooltip.isShowing())
{
gettingInvolvedTooltip.checkAndHide();
}
}
});
}
public void onConnectionStatusChanged(final ConnectionStatusChangeEvent event)
{
refreshConnectionsStatus();
}
public void onConnectionsChanged(final ConnectionsChangedEvent event)
{
refreshConnectionsStatus();
}
public void refreshConnectionsStatus()
{
logger.trace("Refreshing connection status...");
showConnections(controlPanelItem2Controller, button2);
}
public void onConfigurationFileStatusChange(final ConfigurationLoadedEvent event)
{
showConfigurationFileStatus(controlPanelItem1Controller, button1);
}
private void showConfigurationFileStatus(
final ControlPanelItemController controller, final Button button)
{
if (configurationManager.getLoadedConfigurationFile() == null)
{
controller.setTitle("No configuration file found.");
controller.setDetails("Click here display all available options for resolving missing configuration file.");
controller.setStatus(ItemStatus.WARN);
button.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
if (DialogUtils.showDefaultConfigurationFileMissingChoice("Configuration file not found", button.getScene().getWindow()))
{
eventBus.publish(new LoadConfigurationFileEvent(ConfigurationManager.getDefaultConfigurationFile()));
// mainController.loadConfigurationFileOnRunLater(ConfigurationManager.getDefaultConfigurationFile());
}
}
});
}
else
{
button.setOnAction(null);
if (configurationManager.isConfigurationReadOnly())
{
controller.setTitle("Configuration file loaded, but it's read-only.");
controller.setDetails("The configuration that has been loaded from " + configurationManager.getLoadedConfigurationFile().getAbsolutePath() + " is read-only.");
controller.setStatus(ItemStatus.WARN);
}
else
{
controller.setTitle("Configuration file loaded successfully.");
controller.setDetails("The configuration has been loaded from " + configurationManager.getLoadedConfigurationFile().getAbsolutePath() + ".");
controller.setStatus(ItemStatus.OK);
}
}
controller.refresh();
}
private void showPending(final String statusText, final MqttConnectionStatus status,
final MqttAsyncConnection connection, final ConfiguredConnectionDetails connectionDetails,
final Button connectionButton, final String connectionName)
{
connectionButton.getStyleClass().add(StylingUtils.getStyleForMqttConnectionStatus(status));
connectionButton.setOnAction(ActionUtils.createNextAction(status, connection, connectionManager));
final HBox buttonBox = new HBox();
final ProgressIndicator buttonProgress = new ProgressIndicator();
buttonProgress.setMaxSize(15, 15);
buttonBox.getChildren().add(buttonProgress);
buttonBox.getChildren().add(new Label(" " + statusText + " " + connectionName));
connectionButton.setGraphic(buttonBox);
connectionButton.setText(null);
}
private Button createConnectionButton(final ConfiguredConnectionDetails connectionDetails)
{
MqttAsyncConnection connection = null;
for (final MqttAsyncConnection openedConnection : connectionManager.getConnections())
{
if (connectionDetails.getID().equals(openedConnection.getId()))
{
connection = openedConnection;
}
}
final Button connectionButton = new Button();
connectionButton.setFocusTraversable(false);
// final String connectionName = connectionDetails.getFullName();
final String connectionName = connectionDetails.getName();
if (connection != null)
{
logger.trace("Button for " + connectionName + " "
+ connection.getConnectionStatus() + "/" + connection.isOpening() + "/" + connection.isOpened());
}
if (connection == null || (!connection.isOpened() && !connection.isOpening()))
{
final String buttonText = "Open " + connectionName;
connectionButton.getStyleClass().add(StylingUtils.getStyleForMqttConnectionStatus(null));
connectionButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
try
{
connectionManager.openConnection(connectionDetails, mainController);
event.consume();
}
catch (ConfigurationException e)
{
logger.error("Cannot open connection", e);
}
}
});
connectionButton.setText(buttonText);
}
else if (connection.isOpening())
{
showPending("Opening", null, connection, connectionDetails, connectionButton, connectionName);
}
else if (connection.getConnectionStatus() == MqttConnectionStatus.CONNECTING)
{
showPending("Connecting to", connection.getConnectionStatus(), connection, connectionDetails, connectionButton, connectionName);
}
else if (connection.getConnectionStatus() != null)
{
final String buttonText = nextActionTitle.get(connection.getConnectionStatus()) + " " + connectionName;
connectionButton.getStyleClass().add(StylingUtils.getStyleForMqttConnectionStatus(connection.getConnectionStatus()));
connectionButton.setOnAction(ActionUtils.createNextAction(connection.getConnectionStatus(), connection, connectionManager));
connectionButton.setGraphic(null);
connectionButton.setText(buttonText);
}
return connectionButton;
}
public void showConnections(final ControlPanelItemController controller, final Button button)
{
button.setMaxHeight(MAX_CONNECTIONS_HEIGHT);
// Clear any previously displayed connections
while (controller.getCustomItems().getChildren().size() > 2) { controller.getCustomItems().getChildren().remove(2); }
final int connectionCount = configurationManager.getConnections().size();
if (connectionCount > 0)
{
controller.setTitle("You have " + connectionCount + " " + "connection" + (connectionCount > 1 ? "s" : "") + " configured.");
controller.setDetails("Click here to edit your connections or on the relevant button to open, connect, reconnect or disconnect.");
controller.setStatus(ItemStatus.OK);
List<ConfiguredConnectionGroupDetails> groups = configurationManager.getOrderedGroups();
List<Label> labels = new ArrayList<>();
for (final ConfiguredConnectionGroupDetails group : groups)
{
final List<ConfiguredConnectionDetails> connections = configurationManager.getConnections(group);
if (connections.isEmpty())
{
continue;
}
FlowPane buttons = new FlowPane();
buttons.setVgap(4);
buttons.setHgap(4);
buttons.setMaxHeight(Double.MAX_VALUE);
//VBox.setVgrow(buttons, Priority.SOMETIMES);
if (groups.size() > 1)
{
final Label groupLabel = new Label(group.getFullName() + " : ");
// Do some basic alignment
groupLabel.widthProperty().addListener(new ChangeListener<Number>()
{
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue)
{
double maxWidth = 0;
for (final Label label : labels)
{
if (maxWidth < label.getWidth())
{
maxWidth = label.getWidth();
}
}
for (final Label label : labels)
{
logger.trace("Setting min width for " + label.getText() + " to " + maxWidth);
label.setMinWidth(maxWidth);
}
}
});
labels.add(groupLabel);
buttons.getChildren().add(groupLabel);
}
for (final ConfiguredConnectionDetails connection : connections)
// for (final ConfiguredConnectionDetails connection : configurationManager.getConnections())
{
buttons.getChildren().add(createConnectionButton(connection));
}
controller.getCustomItems().getChildren().add(buttons);
button.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
mainController.editConnections();
}
});
}
}
else
{
controller.setTitle("You haven't got any connections configured.");
controller.setDetails("Click here to create a new connection...");
controller.setStatus(ItemStatus.INFO);
button.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
mainController.createNewConnection();
}
});
}
controller.refresh();
}
public void checkForUpdates(final ControlPanelItemController controller, final Button button)
{
button.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
eventBus.publish(new ShowExternalWebPageEvent(configurationManager.getDefaultPropertyFile().getProperty(BasePropertyNames.DOWNLOAD_URL)));
}
});
// Set the default state
controller.setStatus(ItemStatus.INFO);
controller.setTitle("Connecting to the mqtt-spy update server...");
controller.setShowProgress(true);
controller.setDetails("Please wait while mqtt-spy retrieves information about available updates.");
// Run the version check in a separate thread, so that it doesn't block JavaFX
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
versionManager.setLoading(true);
// Wait some time for the app to start properly
ThreadingUtils.sleep(5000);
final MqttSpyVersions versions = versionManager.loadVersions();
logger.debug("Retrieved version info = " + versions.toString());
eventBus.publish(new VersionInfoReceivedEvent(versions));
// eventManager.notifyVersionInfoRetrieved(versions);
}
catch (final XMLException e)
{
// If an error occurred
eventBus.publish(new VersionInfoErrorEvent(e));
// eventManager.notifyVersionInfoError(e);
}
}
}).start();
controller.refresh();
}
public void showUpdateInfo(final ControlPanelItemController controller, final Button button)
{
controller.setShowProgress(false);
final VersionInfoProperties properties = versionManager.getVersionInfoProperties(configurationManager);
controller.setStatus(properties.getStatus());
controller.setTitle(properties.getTitle());
controller.setDetails(properties.getDetails());
// if (versionManager.getVersions() != null)
// {
// boolean versionFound = false;
//
// for (final ReleaseStatus release : versionManager.getVersions().getReleaseStatuses().getReleaseStatus())
// {
// if (VersionManager.isInRange(configurationManager.getDefaultPropertyFile().getFullVersionNumber(), release))
// {
// controller.setStatus(VersionManager.convertVersionStatus(release));
// controller.setTitle(release.getUpdateTitle());
// // TODO: might need to append version info
// controller.setDetails(release.getUpdateDetails());
// versionFound = true;
// break;
// }
// }
//
// if (!versionFound)
// {
// controller.setStatus(ItemStatus.INFO);
// controller.setTitle("Couldn't find any information about your version - please check manually.");
// controller.setDetails("Your version is " + configurationManager.getDefaultPropertyFile().getFullVersionName() + ".");
// }
// }
// else
// {
// // Set the default state
// controller.setStatus(ItemStatus.WARN);
// controller.setTitle("Cannot check for updates - is your internet connection up?");
// controller.setDetails("Click here to go to the download page for mqtt-spy.");
// }
controller.refresh();
}
// ===============================
// === Setters and getters =======
// ===============================
public void setConfigurationMananger(final ConfigurationManager configurationManager)
{
this.configurationManager = configurationManager;
}
public void setMainController(final MainController mainController)
{
this.mainController = mainController;
}
public void setConnectionManager(final ConnectionManager connectionManager)
{
this.connectionManager = connectionManager;
}
public void onVersionInfoReceived(final VersionInfoReceivedEvent event)
{
// If all OK
// Platform.runLater(new Runnable()
// {
// @Override
// public void run()
// {
showUpdateInfo(controlPanelItem3Controller, button3);
// }
// });
}
public void onVersionInfoError(final VersionInfoErrorEvent event)
{
// Platform.runLater(new Runnable()
// {
// @Override
// public void run()
// {
controlPanelItem3Controller.setStatus(ItemStatus.ERROR);
controlPanelItem3Controller.setShowProgress(false);
controlPanelItem3Controller.setTitle("Error occurred while getting version info. Please perform manual update.");
logger.error("Cannot retrieve version info", event.getException());
controlPanelItem3Controller.refresh();
// }
// });
}
public void setVersionManager(VersionManager versionManager)
{
this.versionManager = versionManager;
}
public void setEventBus(final IKBus eventBus)
{
this.eventBus = eventBus;
}
}