/*********************************************************************************** * * Copyright (c) 2015 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.io.File; import java.io.IOException; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.control.MenuButton; import javafx.scene.control.MenuItem; import javafx.scene.control.Tab; import javafx.scene.control.TitledPane; import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.text.TextAlignment; import javafx.stage.FileChooser; import javafx.stage.Modality; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.Window; import javafx.stage.FileChooser.ExtensionFilter; import pl.baczkowicz.mqttspy.Main; import pl.baczkowicz.mqttspy.configuration.ConfigurationManager; import pl.baczkowicz.mqttspy.configuration.ConfiguredConnectionDetails; import pl.baczkowicz.mqttspy.messages.BaseMqttMessage; import pl.baczkowicz.mqttspy.stats.StatisticsManager; import pl.baczkowicz.mqttspy.ui.connections.ConnectionManager; 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.ShowNewSubscriptionWindowEvent; import pl.baczkowicz.mqttspy.ui.events.LoadConfigurationFileEvent; import pl.baczkowicz.mqttspy.ui.events.NewPerspectiveSelectedEvent; import pl.baczkowicz.mqttspy.ui.events.ShowAboutWindowEvent; import pl.baczkowicz.mqttspy.ui.events.ShowEditConnectionsWindowEvent; import pl.baczkowicz.mqttspy.ui.events.ShowExternalWebPageEvent; import pl.baczkowicz.mqttspy.ui.events.ShowFormattersWindowEvent; import pl.baczkowicz.mqttspy.ui.events.ShowMessageLogEvent; import pl.baczkowicz.mqttspy.ui.events.ShowTestCasesWindowEvent; import pl.baczkowicz.mqttspy.ui.messagelog.LogReaderTask; import pl.baczkowicz.mqttspy.ui.messagelog.TaskWithProgressUpdater; import pl.baczkowicz.mqttspy.versions.VersionManager; import pl.baczkowicz.spy.configuration.BaseConfigurationUtils; import pl.baczkowicz.spy.eventbus.IKBus; import pl.baczkowicz.spy.exceptions.ConfigurationException; import pl.baczkowicz.spy.ui.configuration.UiProperties; import pl.baczkowicz.spy.ui.panes.PaneVisibilityStatus; import pl.baczkowicz.spy.ui.panes.SpyPerspective; import pl.baczkowicz.spy.ui.panes.TitledPaneController; import pl.baczkowicz.spy.ui.panes.TitledPaneStatus; import pl.baczkowicz.spy.ui.threading.SimpleRunLaterExecutor; import pl.baczkowicz.spy.ui.utils.FxmlUtils; import pl.baczkowicz.spy.ui.utils.ImageUtils; public class ViewManager { private final static Logger logger = LoggerFactory.getLogger(ViewManager.class); public final static KeyCombination newSubscription = new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN); public final static KeyCombination newPublication = new KeyCodeCombination(KeyCode.P, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN); public static int TITLE_MARGIN = 13; private ConfigurationManager configurationManager; private VersionManager versionManager; private Application application; private IKBus eventBus; private ObservableList<String> stylesheets; private ConnectionManager connectionManager; private StatisticsManager statisticsManager; private SpyPerspective selectedPerspective = SpyPerspective.DEFAULT; // Controllers and stages private Stage aboutStage; private Stage formattersStage; private Stage testCasesStage; private Stage editConnectionsStage; private AboutController aboutController; private EditConnectionsController editConnectionsController; private MainController mainController; public void init() { eventBus.subscribe(this, this::showAbout, ShowAboutWindowEvent.class); eventBus.subscribe(this, this::showFormatters, ShowFormattersWindowEvent.class); eventBus.subscribe(this, this::showTestCases, ShowTestCasesWindowEvent.class); eventBus.subscribe(this, this::loadConfigurationFile, LoadConfigurationFileEvent.class, new SimpleRunLaterExecutor()); eventBus.subscribe(this, this::showEditConnectionsWindow, ShowEditConnectionsWindowEvent.class); eventBus.subscribe(this, this::onNewSelectedPerspective, NewPerspectiveSelectedEvent.class); eventBus.subscribe(this, this::openMessageLog, ShowMessageLogEvent.class); eventBus.subscribe(this, this::showNewSubscriptionWindow, ShowNewSubscriptionWindowEvent.class); TITLE_MARGIN = BaseConfigurationUtils.getIntegerProperty("ui.titlepane.margin", TITLE_MARGIN, configurationManager.getUiPropertyFile()); logger.debug("Property TITLE_MARGIN = {}", TITLE_MARGIN); } private void initialiseAboutWindow(final Window parentWindow) { final FXMLLoader loader = FxmlUtils.createFxmlLoaderForProjectFile("AboutWindow.fxml"); final AnchorPane window = FxmlUtils.loadAnchorPane(loader); aboutController = ((AboutController) loader.getController()); aboutController.setApplication(application); aboutController.setConfigurationManager(configurationManager); aboutController.setVersionManager(versionManager); aboutController.setEventBus(eventBus); aboutController.init(); Scene scene = new Scene(window); scene.getStylesheets().addAll(stylesheets); aboutStage = new Stage(); aboutStage.setTitle("About mqtt-spy"); aboutStage.initModality(Modality.WINDOW_MODAL); aboutStage.initOwner(parentWindow); aboutStage.setScene(scene); } private void initialiseTestCasesWindow(final Window parentWindow) { final FXMLLoader loader = FxmlUtils.createFxmlLoaderForProjectFile("TestCasesExecutionPane.fxml"); final AnchorPane testCasesWindow = FxmlUtils.loadAnchorPane(loader); Scene scene = new Scene(testCasesWindow); scene.getStylesheets().addAll(stylesheets); testCasesStage = new Stage(); testCasesStage.setTitle("Test cases"); testCasesStage.initOwner(parentWindow); testCasesStage.setScene(scene); ((TestCasesExecutionController) loader.getController()).init(); } private void initialiseFormattersWindow(final Window parentWindow) { final FXMLLoader loader = FxmlUtils.createFxmlLoaderForProjectFile("FormattersWindow.fxml"); final AnchorPane formattersWindow = FxmlUtils.loadAnchorPane(loader); final FormattersController formattersController = ((FormattersController) loader.getController()); formattersController.setConfigurationManager(configurationManager); formattersController.setEventBus(eventBus); formattersController.init(); Scene scene = new Scene(formattersWindow); scene.getStylesheets().addAll(stylesheets); formattersStage = new Stage(); formattersStage.setTitle("Formatters"); formattersStage.initModality(Modality.WINDOW_MODAL); formattersStage.initOwner(parentWindow); formattersStage.setScene(scene); } private void initialiseEditConnectionsWindow(final Window parentWindow) { // This is a dirty way to reload connection settings :) possibly could be removed if all connections are closed before loading a new config file if (editConnectionsController != null) { eventBus.unsubscribeConsumer(editConnectionsController, ConnectionStatusChangeEvent.class); } final FXMLLoader loader = FxmlUtils.createFxmlLoaderForProjectFile("EditConnectionsWindow.fxml"); final AnchorPane connectionWindow = FxmlUtils.loadAnchorPane(loader); editConnectionsController = ((EditConnectionsController) loader.getController()); editConnectionsController.setMainController(mainController); editConnectionsController.setEventBus(eventBus); editConnectionsController.setConnectionManager(connectionManager); editConnectionsController.setConfigurationManager(configurationManager); editConnectionsController.init(); Scene scene = new Scene(connectionWindow); scene.getStylesheets().addAll(stylesheets); editConnectionsStage = new Stage(); editConnectionsStage.setTitle("Connection list"); editConnectionsStage.initModality(Modality.WINDOW_MODAL); editConnectionsStage.initOwner(parentWindow); editConnectionsStage.setMinWidth(950); editConnectionsStage.setScene(scene); } @SuppressWarnings({ "rawtypes", "unchecked" }) public Scene createMainWindow(final Stage primaryStage) throws IOException { final FXMLLoader loader = FxmlUtils.createFxmlLoaderForProjectFile("MainWindow.fxml"); // Get the associated pane AnchorPane pane = (AnchorPane) loader.load(); final Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds(); // Set scene width, height and style final double height = Math.min(UiProperties.getApplicationHeight(configurationManager.getUiPropertyFile()), primaryScreenBounds.getHeight()); final double width = Math.min(UiProperties.getApplicationWidth(configurationManager.getUiPropertyFile()), primaryScreenBounds.getWidth()); final Scene scene = new Scene(pane, width, height); scene.getStylesheets().add(Main.class.getResource("application.css").toExternalForm()); stylesheets = scene.getStylesheets(); // Get the associated controller mainController = (MainController) loader.getController(); mainController.setEventBus(eventBus); mainController.setConnectionManager(connectionManager); mainController.setStatisticsManager(statisticsManager); mainController.setVersionManager(versionManager); mainController.setViewManager(this); mainController.setConfigurationManager(configurationManager); mainController.updateSelectedPerspective(UiProperties.getApplicationPerspective(configurationManager.getUiPropertyFile())); mainController.getResizeMessagePaneMenu().setSelected(UiProperties.getResizeMessagePane(configurationManager.getUiPropertyFile())); // Set the stage's properties primaryStage.setScene(scene); primaryStage.setMaximized(UiProperties.getApplicationMaximized(configurationManager.getUiPropertyFile())); // Initialise resources in the main controller mainController.setStage(primaryStage); mainController.setLastHeight(height); mainController.setLastWidth(width); mainController.init(); primaryStage.getIcons().add(ImageUtils.createIcon("mqtt-spy-logo").getImage()); // Set up key shortcuts final KeyCombination newConnection = new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN); final KeyCombination editConnections = new KeyCodeCombination(KeyCode.M, KeyCombination.CONTROL_DOWN); scene.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler() { @Override public void handle(Event event) { if (newConnection.match((KeyEvent) event)) { mainController.createNewConnection(); } else if (editConnections.match((KeyEvent) event)) { mainController.editConnections(); } else if (newSubscription.match((KeyEvent) event)) { final Tab selectedTab = mainController.getConnectionTabs().getSelectionModel().getSelectedItem(); final ConnectionController controller = connectionManager.getControllerForTab(selectedTab); if (controller != null) { eventBus.publish(new ShowNewSubscriptionWindowEvent( controller, PaneVisibilityStatus.DETACHED, controller.getNewSubscriptionPaneStatus().getVisibility())); } } else if (newPublication.match((KeyEvent) event)) { final Tab selectedTab = mainController.getConnectionTabs().getSelectionModel().getSelectedItem(); final ConnectionController controller = connectionManager.getControllerForTab(selectedTab); if (controller != null) { controller.getNewPublicationPaneController().publish(); } } } }); mainController.getNewConnectionMenu().setAccelerator(newConnection); mainController.getEditConnectionsMenu().setAccelerator(editConnections); mainController.getNewSubuscriptionMenu().setAccelerator(newSubscription); return scene; } public void showNewSubscriptionWindow(final ShowNewSubscriptionWindowEvent event) { final ConnectionController connectionController = event.getConnectionController(); if (event.getPreviousStatus().equals(PaneVisibilityStatus.ATTACHED)) { if (!connectionController.getNewSubscriptionPaneController().getTitledPane().isExpanded()) { connectionController.getNewSubscriptionPaneController().getTitledPane().setExpanded(true); } connectionController.getNewSubscriptionPaneController().requestFocus(); } else { final TitledPaneStatus paneStatus = connectionController.getNewSubscriptionPaneStatus(); connectionController.setPaneVisiblity(paneStatus, event.getNewStatus()); if (event.getNewStatus().equals(PaneVisibilityStatus.DETACHED)) { connectionController.getNewSubscriptionPaneController().setPreviousStatus(event.getPreviousStatus()); paneStatus.getParentWhenDetached().setWidth(600); connectionController.getNewSubscriptionPaneController().requestFocus(); } } } public void showAbout(final ShowAboutWindowEvent event) { if (aboutStage == null) { initialiseAboutWindow(event.getParent()); } aboutController.reloadVersionInfo(); aboutStage.show(); } public void showFormatters(final ShowFormattersWindowEvent event) { if (formattersStage == null || !event.getParent().equals(formattersStage.getScene().getWindow())) { initialiseFormattersWindow(event.getParent()); } if (event.isShowAndWait()) { formattersStage.initOwner(event.getParent()); formattersStage.showAndWait(); // Note: removed because we now check for the parent window, and recreate if necessary // formattersStage = null; } else { formattersStage.show(); } } public void showTestCases(final ShowTestCasesWindowEvent event) { if (testCasesStage == null) { initialiseTestCasesWindow(event.getParent()); } testCasesStage.show(); } public void showEditConnectionsWindow(final ShowEditConnectionsWindowEvent event) { logger.debug("showEditConnectionsWindow()"); if (editConnectionsController == null) { initialiseEditConnectionsWindow(event.getParent()); } if (event.isCreateNew()) { editConnectionsController.newMqttConnection(); } if (event.getConnection() != null) { editConnectionsController.selectConnection(event.getConnection().getProperties().getConfiguredProperties()); } editConnectionsController.updateUIForSelectedItem(); editConnectionsController.setPerspective(selectedPerspective); editConnectionsStage.showAndWait(); eventBus.publish(new ConnectionsChangedEvent()); } public void onNewSelectedPerspective(final NewPerspectiveSelectedEvent event) { selectedPerspective = event.getPerspective(); for (final ConnectionController connectionController : connectionManager.getConnectionControllers()) { showPerspective(connectionController); } logger.debug("Selected perspective = " + selectedPerspective.toString()); } public void showPerspective(final ConnectionController connectionController) { switch (selectedPerspective) { case BASIC: connectionController.showPanes(PaneVisibilityStatus.ATTACHED, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.ATTACHED, PaneVisibilityStatus.ATTACHED); break; case DETAILED: connectionController.showPanes(PaneVisibilityStatus.ATTACHED, PaneVisibilityStatus.ATTACHED, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.ATTACHED); break; case SPY: connectionController.showPanes(PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.ATTACHED); break; case SUPER_SPY: connectionController.showPanes(PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.ATTACHED); break; default: connectionController.showPanes(PaneVisibilityStatus.ATTACHED, PaneVisibilityStatus.ATTACHED, PaneVisibilityStatus.NOT_VISIBLE, PaneVisibilityStatus.ATTACHED); break; } connectionController.setViewVisibility(getDetailedViewStatus(selectedPerspective), getBasicViewStatus(selectedPerspective)); } public static boolean getDetailedViewStatus(final SpyPerspective perspective) { switch (perspective) { case BASIC: return false; case DETAILED: return true; case SPY: return false; case SUPER_SPY: return true; default: return false; } } public static boolean getBasicViewStatus(final SpyPerspective perspective) { switch (perspective) { case BASIC: return true; case DETAILED: return false; case SPY: return false; case SUPER_SPY: return false; default: return false; } } public void openMessageLog(final ShowMessageLogEvent event) { final FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select message audit log file to open"); String extensions = "messages"; fileChooser.setSelectedExtensionFilter(new ExtensionFilter("Message audit log file", extensions)); final File selectedFile = fileChooser.showOpenDialog(event.getParent()); if (selectedFile != null) { final TaskWithProgressUpdater<List<BaseMqttMessage>> readAndProcess = new LogReaderTask(selectedFile, connectionManager, mainController); pl.baczkowicz.spy.ui.utils.DialogFactory.createWorkerDialog(readAndProcess); new Thread(readAndProcess).start(); } } public void clear() { connectionManager.disconnectAndCloseAll(); // Only re-initialise if it has been initialised already if (editConnectionsController != null) { initialiseEditConnectionsWindow(mainController.getStage().getScene().getWindow()); } } public void loadConfigurationFile(final LoadConfigurationFileEvent event) { logger.info("Loading configuration file from " + event.getFile().getAbsolutePath()); if (configurationManager.loadConfiguration(event.getFile())) { clear(); // controlPanelPaneController.refreshConnectionsStatus(); eventBus.publish(new ConnectionsChangedEvent()); // Process the connection settings for (final ConfiguredConnectionDetails connection : configurationManager.getConnections()) { if (connection.isAutoOpen() != null && connection.isAutoOpen()) { try { connectionManager.openConnection(connection, mainController); } catch (ConfigurationException e) { // TODO: show warning dialog for invalid logger.error("Cannot open conection {}", connection.getName(), e); } } } } eventBus.publish(new ConfigurationLoadedEvent()); } public void loadDefaultConfigurationFile() { final File defaultConfigurationFile = ConfigurationManager.getDefaultConfigurationFile(); logger.info("Default configuration file present (" + defaultConfigurationFile.getAbsolutePath() + ") = " + defaultConfigurationFile.exists()); if (defaultConfigurationFile.exists()) { eventBus.publish(new LoadConfigurationFileEvent(defaultConfigurationFile)); } else { configurationManager.initialiseConfiguration(); } } public void showExternalWebPage(final ShowExternalWebPageEvent event) { application.getHostServices().showDocument(event.getWebpage()); } public static ChangeListener<Number> createPaneTitleWidthListener(final TitledPane pane, final AnchorPane paneTitle) { return new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { Platform.runLater(new Runnable() { @Override public void run() { ViewManager.updateTitleWidth(pane, paneTitle, TITLE_MARGIN); } }); } }; } public static MenuButton createTitleButton(final String title, final String iconLocation, final double offset, final ConnectionController connectionController, final TitledPane pane) { final MenuButton button = new MenuButton(); button.setId("pane-settings-button"); button.setTooltip(new Tooltip(title)); button.setPadding(Insets.EMPTY); button.setLineSpacing(0); button.setBorder(null); button.setGraphicTextGap(0); button.setFocusTraversable(false); button.setGraphic(ImageUtils.createIcon(iconLocation, 14)); button.setStyle("-fx-background-color: transparent;"); // TODO: actions final MenuItem detach = new MenuItem("Detach to a separate window", ImageUtils.createIcon("tab-detach", 14, "pane-settings-menu-graphic")); detach.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { connectionController.setPaneVisiblity( connectionController.getPaneToStatusMapping().get(pane), PaneVisibilityStatus.DETACHED); } }); final MenuItem hide = new MenuItem("Hide this pane", ImageUtils.createIcon("tab-close", 14, "pane-settings-menu-graphic")); hide.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { connectionController.setPaneVisiblity( connectionController.getPaneToStatusMapping().get(pane), PaneVisibilityStatus.NOT_VISIBLE); } }); button.getItems().add(detach); button.getItems().add(hide); for (MenuItem item : button.getItems()) { item.getStyleClass().add("pane-settings-menu-item"); } button.setTextAlignment(TextAlignment.RIGHT); button.setAlignment(Pos.CENTER_RIGHT); AnchorPane.setRightAnchor(button, offset); return button; } public static MenuButton createTitleButtons(final TitledPaneController controller, //final TitledPane pane, final AnchorPane paneTitle, final ConnectionController connectionController) { final TitledPane pane = controller.getTitledPane(); final MenuButton settingsButton = createTitleButton("Pane settings", "settings", -5, connectionController, pane); HBox titleBox = new HBox(); titleBox.setPadding(new Insets(0, 0, 0, 0)); logger.trace(pane + ", " + paneTitle + ", " + connectionController); titleBox.getChildren().addAll(controller.getTitleLabel()); titleBox.prefWidth(Double.MAX_VALUE); paneTitle.setPadding(new Insets(0, 0, 0, 0)); paneTitle.getChildren().addAll(titleBox, settingsButton); paneTitle.setMaxWidth(Double.MAX_VALUE); pane.setText(null); pane.setGraphic(paneTitle); pane.widthProperty().addListener(createPaneTitleWidthListener(pane, paneTitle)); return settingsButton; } public static double updateTitleWidth(final TitledPane titledPane, final AnchorPane paneTitle, final double margin) { final double marginWithPositionOfset = margin + paneTitle.getLayoutX(); double titledPaneWidth = titledPane.getWidth(); //logger.debug("{} titledPane.getWidth() = {}", titledPane, titledPaneWidth); if (titledPane.getScene() != null) { if (titledPane.getScene().getWidth() < titledPaneWidth) { titledPaneWidth = titledPane.getScene().getWidth(); //logger.debug("{} titledPane.getScene().getWidth() = {}", titledPane, titledPaneWidth); } } paneTitle.setPrefWidth(titledPaneWidth - marginWithPositionOfset); paneTitle.setMaxWidth(titledPaneWidth - marginWithPositionOfset); return titledPaneWidth; } // ************ /** * Sets the configuration manager. * * @param configurationManager the configurationManager to set */ public void setConfigurationManager(ConfigurationManager configurationManager) { this.configurationManager = configurationManager; } /** * Sets the event bus. * * @param eventBus the eventBus to set */ public void setEventBus(final IKBus eventBus) { this.eventBus = eventBus; } public void setVersionManager(final VersionManager versionManager) { this.versionManager = versionManager; } public void setApplication(Application application) { this.application = application; } public void setConnectionManager(final ConnectionManager connectionManager) { this.connectionManager = connectionManager; } public void setStatisticsManager(final StatisticsManager statisticsManager) { this.statisticsManager = statisticsManager; } public MainController getMainController() { return mainController; } public SpyPerspective getPerspective() { return selectedPerspective; } }