/***********************************************************************************
*
* 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.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.stage.DirectoryChooser;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.baczkowicz.mqttspy.connectivity.MqttAsyncConnection;
import pl.baczkowicz.mqttspy.ui.scripts.InteractiveScriptManager;
import pl.baczkowicz.spy.eventbus.IKBus;
import pl.baczkowicz.spy.scripts.ScriptRunningState;
import pl.baczkowicz.spy.scripts.events.ScriptStateChangeEvent;
import pl.baczkowicz.spy.ui.panes.PaneVisibilityStatus;
import pl.baczkowicz.spy.ui.panes.TitledPaneController;
import pl.baczkowicz.spy.ui.properties.PublicationScriptProperties;
import pl.baczkowicz.spy.ui.scripts.ScriptTypeEnum;
import pl.baczkowicz.spy.ui.scripts.events.ScriptListChangeEvent;
import pl.baczkowicz.spy.ui.threading.SimpleRunLaterExecutor;
import pl.baczkowicz.spy.ui.utils.DialogFactory;
import pl.baczkowicz.spy.ui.utils.UiUtils;
/**
* Controller for publications scripts pane.
*/
public class PublicationScriptsController implements Initializable, TitledPaneController
{
/** Diagnostic logger. */
private final static Logger logger = LoggerFactory.getLogger(PublicationScriptsController.class);
@FXML
private TableView<PublicationScriptProperties> scriptTable;
@FXML
private TableColumn<PublicationScriptProperties, String> nameColumn;
@FXML
private TableColumn<PublicationScriptProperties, ScriptTypeEnum> typeColumn;
@FXML
private TableColumn<PublicationScriptProperties, Boolean> repeatColumn;
@FXML
private TableColumn<PublicationScriptProperties, ScriptRunningState> runningStatusColumn;
@FXML
private TableColumn<PublicationScriptProperties, String> lastPublishedColumn;
@FXML
private TableColumn<PublicationScriptProperties, Long> messageCountColumn;
private MqttAsyncConnection connection;
private InteractiveScriptManager scriptManager;
private IKBus eventBus;
// private EventManager<FormattedMqttMessage> eventManager;
private Map<ScriptTypeEnum, ContextMenu> contextMenus = new HashMap<>();
private TitledPane pane;
private AnchorPane paneTitle;
private MenuButton settingsButton;
private ConnectionController connectionController;
private Label titleLabel;
private String publicationScriptsDirectory;
private boolean includeSubdirectories;
public void initialize(URL location, ResourceBundle resources)
{
// Table
nameColumn.setCellValueFactory(new PropertyValueFactory<PublicationScriptProperties, String>("name"));
typeColumn.setCellValueFactory(new PropertyValueFactory<PublicationScriptProperties, ScriptTypeEnum>("type"));
typeColumn
.setCellFactory(new Callback<TableColumn<PublicationScriptProperties, ScriptTypeEnum>, TableCell<PublicationScriptProperties, ScriptTypeEnum>>()
{
public TableCell<PublicationScriptProperties, ScriptTypeEnum> call(
TableColumn<PublicationScriptProperties, ScriptTypeEnum> param)
{
final TableCell<PublicationScriptProperties, ScriptTypeEnum> cell = new TableCell<PublicationScriptProperties, ScriptTypeEnum>()
{
@Override
public void updateItem(ScriptTypeEnum item, boolean empty)
{
super.updateItem(item, empty);
if (!isEmpty())
{
setText(item.toString());
}
else
{
setText(null);
setGraphic(null);
}
}
};
cell.setAlignment(Pos.TOP_CENTER);
return cell;
}
});
repeatColumn.setCellValueFactory(new PropertyValueFactory<PublicationScriptProperties, Boolean>("repeat"));
repeatColumn
.setCellFactory(new Callback<TableColumn<PublicationScriptProperties, Boolean>, TableCell<PublicationScriptProperties, Boolean>>()
{
public TableCell<PublicationScriptProperties, Boolean> call(
TableColumn<PublicationScriptProperties, Boolean> param)
{
final CheckBoxTableCell<PublicationScriptProperties, Boolean> cell = new CheckBoxTableCell<PublicationScriptProperties, Boolean>()
{
@Override
public void updateItem(final Boolean checked, boolean empty)
{
super.updateItem(checked, empty);
if (!isEmpty() && checked != null && this.getTableRow() != null && this.getTableRow().getItem() != null)
{
final PublicationScriptProperties item = (PublicationScriptProperties) this.getTableRow().getItem();
// Anything but subscription scripts can be repeated
if (!ScriptTypeEnum.SUBSCRIPTION.equals(item.typeProperty().getValue()))
{
this.setDisable(false);
if (logger.isTraceEnabled())
{
logger.trace("Setting repeat for {} to {}", item.getScript().getName(), checked);
}
item.setRepeat(checked);
}
else
{
this.setDisable(true);
}
}
}
};
cell.setAlignment(Pos.TOP_CENTER);
return cell;
}
});
messageCountColumn.setCellValueFactory(new PropertyValueFactory<PublicationScriptProperties, Long>("count"));
messageCountColumn
.setCellFactory(new Callback<TableColumn<PublicationScriptProperties, Long>, TableCell<PublicationScriptProperties, Long>>()
{
public TableCell<PublicationScriptProperties, Long> call(
TableColumn<PublicationScriptProperties, Long> param)
{
final TableCell<PublicationScriptProperties, Long> cell = new TableCell<PublicationScriptProperties, Long>()
{
@Override
public void updateItem(Long item, boolean empty)
{
super.updateItem(item, empty);
if (!isEmpty())
{
setText(item.toString());
}
else
{
setText(null);
}
}
};
cell.setAlignment(Pos.TOP_CENTER);
return cell;
}
});
runningStatusColumn.setCellValueFactory(new PropertyValueFactory<PublicationScriptProperties, ScriptRunningState>("status"));
runningStatusColumn
.setCellFactory(new Callback<TableColumn<PublicationScriptProperties, ScriptRunningState>, TableCell<PublicationScriptProperties, ScriptRunningState>>()
{
public TableCell<PublicationScriptProperties, ScriptRunningState> call(
TableColumn<PublicationScriptProperties, ScriptRunningState> param)
{
final TableCell<PublicationScriptProperties, ScriptRunningState> cell = new TableCell<PublicationScriptProperties, ScriptRunningState>()
{
@Override
public void updateItem(ScriptRunningState item, boolean empty)
{
super.updateItem(item, empty);
if (!isEmpty())
{
setText(item.toString());
}
else
{
setText(null);
setGraphic(null);
}
}
};
cell.setAlignment(Pos.TOP_CENTER);
return cell;
}
});
lastPublishedColumn.setCellValueFactory(new PropertyValueFactory<PublicationScriptProperties, String>("lastPublished"));
lastPublishedColumn.setCellFactory(new Callback<TableColumn<PublicationScriptProperties, String>, TableCell<PublicationScriptProperties, String>>()
{
public TableCell<PublicationScriptProperties, String> call(
TableColumn<PublicationScriptProperties, String> param)
{
final TableCell<PublicationScriptProperties, String> cell = new TableCell<PublicationScriptProperties, String>()
{
@Override
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if (!isEmpty())
{
setText(item.toString());
}
else
{
setText(null);
}
}
};
cell.setAlignment(Pos.TOP_CENTER);
return cell;
}
});
scriptTable
.setRowFactory(new Callback<TableView<PublicationScriptProperties>, TableRow<PublicationScriptProperties>>()
{
public TableRow<PublicationScriptProperties> call(
TableView<PublicationScriptProperties> tableView)
{
final TableRow<PublicationScriptProperties> row = new TableRow<PublicationScriptProperties>()
{
@Override
protected void updateItem(PublicationScriptProperties item, boolean empty)
{
super.updateItem(item, empty);
if (!isEmpty() && item != null)
{
final ContextMenu rowMenu = contextMenus.get(item.typeProperty().getValue());
this.setContextMenu(rowMenu);
}
}
};
row.setOnMouseClicked(event ->
{
if (event.getClickCount() == 2 && !row.isEmpty())
{
startScript();
}
});
return row;
}
});
scriptTable.setPlaceholder(new Label("No scripts found - edit your connection settings or use the context menu to set script directory."));
}
public void init()
{
titleLabel = new Label(pane.getText());
includeSubdirectories = true;
scriptManager = connection.getScriptManager();
eventBus.subscribe(this, this::onScriptStateChange, ScriptStateChangeEvent.class, new SimpleRunLaterExecutor());
// TODO: replaced with event bus; to be deleted
// eventManager.registerScriptStateChangeObserver(this, null);
refreshList();
scriptTable.setItems(scriptManager.getObservableScriptList());
// Note: subscription scripts don't have context menus because they can't be started/stopped manually - for future, consider enabled/disabled
contextMenus.put(ScriptTypeEnum.PUBLICATION, createDirectoryTypeScriptTableContextMenu(ScriptTypeEnum.PUBLICATION));
contextMenus.put(ScriptTypeEnum.BACKGROUND, createDirectoryTypeScriptTableContextMenu(ScriptTypeEnum.BACKGROUND));
contextMenus.put(ScriptTypeEnum.SUBSCRIPTION, createDirectoryTypeScriptTableContextMenu(ScriptTypeEnum.SUBSCRIPTION));
scriptTable.setContextMenu(createScriptTableContextMenu());
paneTitle = new AnchorPane();
settingsButton = ViewManager.createTitleButtons(this, paneTitle, connectionController);
publicationScriptsDirectory = InteractiveScriptManager.getScriptDirectoryForConnection(
connection.getProperties().getConfiguredProperties().getPublicationScripts());
scriptTable.getSortOrder().clear();
scriptTable.getSortOrder().add(nameColumn);
}
private void refreshList()
{
// Directory-driven
scriptManager.addScripts(publicationScriptsDirectory, includeSubdirectories, ScriptTypeEnum.PUBLICATION);
// As defined
scriptManager.addScripts(connection.getProperties().getConfiguredProperties().getBackgroundScript(), ScriptTypeEnum.BACKGROUND);
// Subscription-based
scriptManager.addSubscriptionScripts(connection.getProperties().getConfiguredProperties().getSubscription());
// TODO: move this to script manager?
eventBus.publish(new ScriptListChangeEvent(connection));
}
public void setConnection(MqttAsyncConnection connection)
{
this.connection = connection;
}
public void startScript(final PublicationScriptProperties item)
{
scriptManager.runScript(item.getScript(), true);
}
public void stopScript(final File file)
{
scriptManager.stopScriptFile(file);
}
private void startScript()
{
final PublicationScriptProperties item = scriptTable.getSelectionModel().getSelectedItem();
if (item != null
&& (item.typeProperty().getValue().equals(ScriptTypeEnum.BACKGROUND)
|| item.typeProperty().getValue().equals(ScriptTypeEnum.PUBLICATION)))
{
startScript(item);
}
}
private ContextMenu createScriptTableContextMenu()
{
return new ContextMenu(
createSetScriptDirectoryMenuItem(),
createIncludeSubdirectoriesMenuItem(),
new SeparatorMenuItem(),
createRefreshListMenuItem());
}
public ContextMenu createDirectoryTypeScriptTableContextMenu(final ScriptTypeEnum type)
{
final ContextMenu contextMenu = new ContextMenu();
if (ScriptTypeEnum.PUBLICATION.equals(type) || ScriptTypeEnum.BACKGROUND.equals(type))
{
// Start script
final MenuItem startScriptItem = new MenuItem("Start");
startScriptItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
startScript();
}
});
contextMenu.getItems().add(startScriptItem);
// Stop script
final MenuItem stopScriptItem = new MenuItem("Stop");
stopScriptItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
final PublicationScriptProperties item = scriptTable.getSelectionModel()
.getSelectedItem();
if (item != null)
{
stopScript(item.getScript().getScriptFile());
}
}
});
contextMenu.getItems().add(stopScriptItem);
// Separator
contextMenu.getItems().add(new SeparatorMenuItem());
}
// Copy script location
final MenuItem copyScriptLocationItem = new MenuItem("Copy script location to clipboard");
copyScriptLocationItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
final PublicationScriptProperties item = scriptTable.getSelectionModel()
.getSelectedItem();
if (item != null)
{
UiUtils.copyToClipboard(item.getScript().getScriptFile().getAbsolutePath());
}
}
});
contextMenu.getItems().add(copyScriptLocationItem);
// Separator
contextMenu.getItems().add(new SeparatorMenuItem());
if (type.equals(ScriptTypeEnum.PUBLICATION))
{
// Delete
final Menu deleteItem = new Menu("Delete");
contextMenu.getItems().add(deleteItem);
final MenuItem deleteFromListItem = new MenuItem("Delete from list (until next refresh)");
deleteFromListItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
final PublicationScriptProperties item = scriptTable.getSelectionModel().getSelectedItem();
if (item != null)
{
scriptManager.removeScript(item);
}
}
});
deleteItem.getItems().add(deleteFromListItem);
final MenuItem deleteFromDiskItem = new MenuItem("Delete from disk (permanently)");
deleteFromDiskItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
final PublicationScriptProperties item = scriptTable.getSelectionModel().getSelectedItem();
if (item != null)
{
scriptManager.removeScript(item);
if (!item.getScript().getScriptFile().delete())
{
DialogFactory.createWarningDialog("File cannot be deleted",
"File \"" + item.getScript().getScriptFile().getAbsolutePath() + "\" couln't be deleted. Try doing it manually.");
}
}
refreshList();
}
});
deleteItem.getItems().add(deleteFromDiskItem);
// Separator
contextMenu.getItems().add(new SeparatorMenuItem());
}
contextMenu.getItems().addAll( createSetScriptDirectoryMenuItem(),
createIncludeSubdirectoriesMenuItem(),
new SeparatorMenuItem(),
createRefreshListMenuItem());
return contextMenu;
}
private MenuItem createSetScriptDirectoryMenuItem()
{
// Set directory
final MenuItem setDirectoryItem = new MenuItem("Set script directory...");
setDirectoryItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
final File initialDirectory = new File(publicationScriptsDirectory);
final DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("Select the script directory");
chooser.setInitialDirectory(initialDirectory);
final File selectedDirectory = chooser.showDialog(scriptTable.getScene().getWindow());
if (selectedDirectory != null)
{
publicationScriptsDirectory = selectedDirectory.getAbsolutePath();
clearAndRefesh();
}
}
});
return setDirectoryItem;
}
private void clearAndRefesh()
{
// Clear current scripts
scriptManager.getObservableScriptList().clear();
scriptManager.getScriptsMap().clear();
scriptTable.getItems().clear();
refreshList();
scriptTable.getSortOrder().clear();
scriptTable.getSortOrder().add(nameColumn);
}
private MenuItem createIncludeSubdirectoriesMenuItem()
{
// Refresh list
final CheckMenuItem includeSubdirectoriesItem = new CheckMenuItem("Include subdirectories (recursive)");
includeSubdirectoriesItem.setSelected(includeSubdirectories);
includeSubdirectoriesItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
includeSubdirectories = !includeSubdirectories;
clearAndRefesh();
}
});
return includeSubdirectoriesItem;
}
private MenuItem createRefreshListMenuItem()
{
// Refresh list
final MenuItem refreshListItem = new MenuItem("Refresh list");
refreshListItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
refreshList();
}
});
return refreshListItem;
}
public void onScriptStateChange(final ScriptStateChangeEvent event)
{
// TODO: update the context menu - but this requires context menu per row, not type
}
@Override
public TitledPane getTitledPane()
{
return pane;
}
@Override
public void setTitledPane(TitledPane pane)
{
this.pane = pane;
}
@Override
public void updatePane(PaneVisibilityStatus status)
{
if (PaneVisibilityStatus.ATTACHED.equals(status))
{
settingsButton.setVisible(true);
}
else
{
settingsButton.setVisible(false);
}
}
public void setConnectionController(ConnectionController connectionController)
{
this.connectionController = connectionController;
}
public void setEventBus(final IKBus eventBus)
{
this.eventBus = eventBus;
}
@Override
public Label getTitleLabel()
{
return titleLabel;
}
}