/*
*
* * Copyright (C) 2015 CS SI
* *
* * This program is free software; you can redistribute it and/or modify it
* * under the terms of the GNU General Public License as published by the Free
* * Software Foundation; either version 3 of the License, or (at your option)
* * any later version.
* * This program is distributed in the hope that it will be useful, but WITHOUT
* * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* * more details.
* *
* * You should have received a copy of the GNU General Public License along
* * with this program; if not, see http://www.gnu.org/licenses/
*
*/
package org.esa.snap.ui.tooladapter.dialogs;
import org.esa.snap.core.gpf.GPF;
import org.esa.snap.core.gpf.descriptor.ToolAdapterOperatorDescriptor;
import org.esa.snap.core.gpf.operators.tooladapter.*;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.tango.TangoIcons;
import org.esa.snap.ui.AppContext;
import org.esa.snap.ui.ModelessDialog;
import org.esa.snap.ui.tooladapter.actions.EscapeAction;
import org.esa.snap.ui.tooladapter.actions.ToolAdapterActionRegistrar;
import org.esa.snap.ui.tooladapter.model.OperationType;
import org.esa.snap.ui.tooladapter.model.OperatorsTableModel;
import org.esa.snap.utils.AdapterWatcher;
import org.openide.util.NbBundle;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import static org.esa.snap.utils.SpringUtilities.DEFAULT_PADDING;
import static org.esa.snap.utils.SpringUtilities.makeCompactGrid;
/**
* Dialog that allows the management (create, edit, remove and execute) of external
* tool adapters
*
* @author Ramona Manda
* @author Cosmin Cara
*/
@NbBundle.Messages({
"Dialog_Title=External Tools",
"ToolTipNewOperator_Text=Define new operator",
"ToolTipCopyOperator_Text=Duplicate the selected operator",
"ToolTipEditOperator_Text=Edit the selected operator",
"ToolTipExecuteOperator_Text=Execute the selected operator",
"ToolTipDeleteOperator_Text=Delete the selected operator",
"PathLabel_Text=Adapters location",
"MessageNoSelection_Text=Please select an adapter first",
"MessageConfirmRemoval_TitleText=Confirm removal",
"MessageConfirmRemoval_Text=Are you sure you want to remove the selected adapter?\nThe operation will delete also the associated folder and files",
"MessageConfirmRemovalDontAsk_Text=Don't ask me in the future",
"MessagePackageModules_Text=The adapter %s was installed as a NetBeans module and cannot be removed from here.%nPlease uninstall the module."
})
public class ToolAdaptersManagementDialog extends ModelessDialog {
final int CHECK_COLUMN_WIDTH = 20;
final int LABEL_COLUMN_WIDTH = 250;
final int COLUMN_WIDTH = 270;
final int PATH_ROW_HEIGHT = 20;
final int BUTTON_HEIGHT = 32;
final Dimension buttonDimension = new Dimension((CHECK_COLUMN_WIDTH + LABEL_COLUMN_WIDTH + COLUMN_WIDTH) / 5, BUTTON_HEIGHT);
private AppContext appContext;
private JTable operatorsTable = null;
private Logger logger = Logger.getLogger(ToolAdaptersManagementDialog.class.getName());
private static ToolAdaptersManagementDialog instance;
public static void showDialog(AppContext appContext, String helpID) {
if (instance == null) {
instance = new ToolAdaptersManagementDialog(appContext, Bundle.Dialog_Title(), helpID);
}
instance.show();
}
ToolAdaptersManagementDialog(AppContext appContext, String title, String helpID) {
super(appContext.getApplicationWindow(), title, 0, helpID);
this.appContext = appContext;
JPanel contentPanel = createContentPanel();
setContent(contentPanel);
super.getJDialog().setMinimumSize(contentPanel.getPreferredSize());
EscapeAction.register(super.getJDialog());
ToolAdapterRegistry.INSTANCE.addListener(new ToolAdapterListener() {
@Override
public void adapterAdded(ToolAdapterOperatorDescriptor operatorDescriptor) {
refreshContent();
}
@Override
public void adapterRemoved(ToolAdapterOperatorDescriptor operatorDescriptor) {
refreshContent();
}
});
}
private JPanel createContentPanel() {
//compute content and other buttons
SpringLayout springLayout = new SpringLayout();
JPanel panel = new JPanel(springLayout);
int panelHeight = 0;
JTable propertiesPanel = createPropertiesPanel();
panelHeight += propertiesPanel.getPreferredSize().getHeight();
panel.add(propertiesPanel);
panelHeight += 10;
panel.add(Box.createVerticalStrut(10));
JScrollPane scrollPane = new JScrollPane(createAdaptersPanel());
panelHeight += scrollPane.getPreferredSize().getHeight();
panel.add(scrollPane);
panelHeight += 10;
panel.add(Box.createVerticalStrut(10));
JPanel buttonsPanel = createButtonsPanel();
panelHeight += buttonsPanel.getPreferredSize().getHeight();
panel.add(buttonsPanel);
springLayout.putConstraint(SpringLayout.NORTH, panel, DEFAULT_PADDING, SpringLayout.NORTH, propertiesPanel);
springLayout.putConstraint(SpringLayout.WEST, panel, DEFAULT_PADDING, SpringLayout.WEST, propertiesPanel);
springLayout.putConstraint(SpringLayout.EAST, panel, DEFAULT_PADDING, SpringLayout.EAST, propertiesPanel);
springLayout.putConstraint(SpringLayout.NORTH, scrollPane, DEFAULT_PADDING, SpringLayout.SOUTH, propertiesPanel);
springLayout.putConstraint(SpringLayout.WEST, panel, DEFAULT_PADDING, SpringLayout.WEST, scrollPane );
springLayout.putConstraint(SpringLayout.EAST, panel, DEFAULT_PADDING, SpringLayout.EAST, scrollPane);
springLayout.putConstraint(SpringLayout.NORTH, scrollPane, DEFAULT_PADDING, SpringLayout.SOUTH, buttonsPanel);
springLayout.putConstraint(SpringLayout.WEST, panel, DEFAULT_PADDING, SpringLayout.WEST, buttonsPanel);
springLayout.putConstraint(SpringLayout.EAST, panel, DEFAULT_PADDING, SpringLayout.EAST, buttonsPanel);
panel.setPreferredSize(new Dimension(CHECK_COLUMN_WIDTH + LABEL_COLUMN_WIDTH + COLUMN_WIDTH - 32, panelHeight + DEFAULT_PADDING));
makeCompactGrid(panel, 5, 1, 0, 0, 0, 0);
return panel;
}
private JPanel createButtonsPanel() {
JPanel panel = new JPanel(new SpringLayout());
/**
* New adapter button
*/
panel.add(createButton("New",
TangoIcons.actions_document_new(TangoIcons.Res.R22),
Bundle.ToolTipNewOperator_Text(),
e -> {
ToolAdapterOperatorDescriptor newOperatorSpi = new ToolAdapterOperatorDescriptor(ToolAdapterConstants.OPERATOR_NAMESPACE + "NewOperator", ToolAdapterOp.class, "NewOperator", null, null, null, null, null, null);
AbstractAdapterEditor dialog = AbstractAdapterEditor.createEditorDialog(appContext, getJDialog(), newOperatorSpi, OperationType.NEW);
dialog.show();
refreshContent();
}));
/**
* Duplicate adapter button
*/
panel.add(createButton("Copy",
TangoIcons.actions_edit_copy(TangoIcons.Res.R22),
Bundle.ToolTipCopyOperator_Text(),
e -> {
ToolAdapterOperatorDescriptor operatorDesc = requestSelection();
if (operatorDesc != null) {
String opName = operatorDesc.getName();
int newNameIndex = 0;
while (GPF.getDefaultInstance().getOperatorSpiRegistry().getOperatorSpi(opName) != null) {
newNameIndex++;
opName = operatorDesc.getName() + ToolAdapterConstants.OPERATOR_GENERATED_NAME_SEPARATOR + newNameIndex;
}
AbstractAdapterEditor dialog = AbstractAdapterEditor.createEditorDialog(appContext, getJDialog(), operatorDesc, newNameIndex, OperationType.COPY);
dialog.show();
refreshContent();
}
}));
/**
* Edit adapter button
*/
panel.add(createButton("Edit",
TangoIcons.apps_accessories_text_editor(TangoIcons.Res.R22),
Bundle.ToolTipEditOperator_Text(),
e -> {
ToolAdapterOperatorDescriptor operatorDesc = requestSelection();
if (operatorDesc != null) {
AbstractAdapterEditor dialog = AbstractAdapterEditor.createEditorDialog(appContext, getJDialog(), operatorDesc, OperationType.EDIT);
dialog.show();
refreshContent();
}
}));
/**
* Delete adapter button
*/
panel.add(createButton("Delete",
TangoIcons.actions_edit_clear(TangoIcons.Res.R22),
Bundle.ToolTipDeleteOperator_Text(),
e -> {
ToolAdapterOperatorDescriptor operatorDescriptor = requestSelection();
if (operatorDescriptor != null) {
if (Dialogs.Answer.YES == Dialogs.requestDecision(Bundle.MessageConfirmRemoval_TitleText(),
Bundle.MessageConfirmRemoval_Text(), true,
Bundle.MessageConfirmRemovalDontAsk_Text())) {
if (operatorDescriptor.isFromPackage()) {
Dialogs.showWarning(String.format(Bundle.MessagePackageModules_Text(), operatorDescriptor.getName()));
} else {
//ToolAdapterActionRegistrar.removeOperatorMenu(operatorDescriptor);
ToolAdapterIO.removeOperator(operatorDescriptor);
}
refreshContent();
}
}
}));
/**
* Execute adapter button
*/
panel.add(createButton("Run",
TangoIcons.actions_media_playback_start(TangoIcons.Res.R22),
Bundle.ToolTipExecuteOperator_Text(),
e -> {
ToolAdapterOperatorDescriptor operatorDesc = requestSelection();
if (operatorDesc != null) {
//close();
final ToolAdapterExecutionDialog operatorDialog = new ToolAdapterExecutionDialog(
operatorDesc,
appContext,
operatorDesc.getLabel());
operatorDialog.getJDialog().setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
operatorDialog.show();
}
}));
makeCompactGrid(panel, 1, 5, 0, 0, DEFAULT_PADDING, DEFAULT_PADDING);
return panel;
}
private AbstractButton createButton(String text, ImageIcon icon, String toolTip, ActionListener actionListener) {
AbstractButton button = new JButton(text, icon);
button.setMaximumSize(buttonDimension);
button.setPreferredSize(buttonDimension);
if (toolTip != null) {
button.setToolTipText(toolTip);
}
if (actionListener != null) {
button.addActionListener(actionListener);
}
return button;
}
private JTable createPropertiesPanel() {
DefaultTableModel model = new DefaultTableModel(1, 2) {
@Override
public boolean isCellEditable(int row, int column) {
return column == 1;
}
};
model.setValueAt(Bundle.PathLabel_Text(), 0, 0);
model.setValueAt(ToolAdapterIO.getAdaptersPath(), 0, 1);
model.addTableModelListener(l -> {
String newPath = model.getValueAt(0, 1).toString();
Path path = Paths.get(newPath);
Path oldPath = ToolAdapterIO.getAdaptersPath();
try {
if (Files.isSameFile(oldPath, path)) {
return;
}
} catch (IOException ignored) {
}
if (!Files.exists(path) &&
Dialogs.Answer.YES == Dialogs.requestDecision("Path does not exist", "The path you have entered does not exist.\nDo you want to create it?", true, "Don't ask me in the future")) {
try {
Files.createDirectories(Files.isDirectory(path) ? path : path.getParent());
} catch (IOException ex) {
Dialogs.showError("Path could not be created!");
}
}
if (Files.exists(path)) {
ToolAdapterOperatorDescriptor[] operatorDescriptors = ToolAdapterActionRegistrar.getActionMap().values()
.toArray(new ToolAdapterOperatorDescriptor[ToolAdapterActionRegistrar.getActionMap().values().size()]);
for (ToolAdapterOperatorDescriptor descriptor : operatorDescriptors) {
ToolAdapterIO.removeOperator(descriptor, false);
}
AdapterWatcher.INSTANCE.unmonitorPath(oldPath);
ToolAdapterIO.setAdaptersPath(path);
if (!newPath.equals(oldPath.toAbsolutePath().toString())) {
ToolAdapterIO.searchAndRegisterAdapters();
refreshContent();
}
try {
AdapterWatcher.INSTANCE.monitorPath(path);
} catch (IOException e) {
logger.warning(String.format("Could not watch for the new adapter path %s [%s]", path.toString(), e.getMessage()));
}
}
});
JTable table = new JTable(model);
TableColumn labelColumn = table.getColumnModel().getColumn(0);
labelColumn.setPreferredWidth((CHECK_COLUMN_WIDTH + LABEL_COLUMN_WIDTH)/2);
TableColumn pathColumn = table.getColumnModel().getColumn(1);
pathColumn.setPreferredWidth(COLUMN_WIDTH);
pathColumn.setCellEditor(new FileChooserCellEditor());
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
table.setRowHeight(PATH_ROW_HEIGHT);
table.setBorder(BorderFactory.createLineBorder(Color.black));
table.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
}
@Override
public void focusLost(FocusEvent e) {
Object source = e.getSource();
if (!table.equals(source)) {
table.editingCanceled(new ChangeEvent(source));
table.clearSelection();
}
}
});
return table;
}
private JTable createAdaptersPanel() {
java.util.List<ToolAdapterOperatorDescriptor> toolboxSpis = new ArrayList<>();
toolboxSpis.addAll(ToolAdapterRegistry.INSTANCE.getOperatorMap().values()
.stream()
.map(e -> (ToolAdapterOperatorDescriptor) e.getOperatorDescriptor())
.collect(Collectors.toList()));
toolboxSpis.sort((o1, o2) -> o1.getAlias().compareTo(o2.getAlias()));
OperatorsTableModel model = new OperatorsTableModel(toolboxSpis);
operatorsTable = new JTable(model);
operatorsTable.getColumnModel().getColumn(0).setPreferredWidth(LABEL_COLUMN_WIDTH);
operatorsTable.getColumnModel().getColumn(0).setMaxWidth(300);
operatorsTable.getColumnModel().getColumn(1).setResizable(true);
operatorsTable.getColumnModel().getColumn(1).setPreferredWidth(LABEL_COLUMN_WIDTH);
operatorsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
operatorsTable.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() >= 2) {
int selectedRow = operatorsTable.getSelectedRow();
operatorsTable.repaint();
ToolAdapterOperatorDescriptor operatorDesc = ((OperatorsTableModel) operatorsTable.getModel()).getObjectAt(selectedRow);
AbstractAdapterEditor dialog = AbstractAdapterEditor.createEditorDialog(appContext, getJDialog(), operatorDesc, OperationType.EDIT);
dialog.show();
refreshContent();
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
});
return operatorsTable;
}
private ToolAdapterOperatorDescriptor requestSelection() {
ToolAdapterOperatorDescriptor selected = null;
int selectedRow = operatorsTable.getSelectedRow();
if (selectedRow >= 0) {
selected = ((OperatorsTableModel) operatorsTable.getModel()).getObjectAt(selectedRow);
} else {
Dialogs.showWarning(Bundle.MessageNoSelection_Text());
}
return selected;
}
private void refreshContent() {
setContent(createContentPanel());
getContent().repaint();
}
public class FileChooserCellEditor extends DefaultCellEditor implements TableCellEditor {
/** Number of clicks to start editing */
private static final int CLICK_COUNT_TO_START = 2;
/** Editor component */
private JButton button;
/** File chooser */
private JFileChooser fileChooser;
/** Selected file */
private String file = "";
/**
* Constructor.
*/
public FileChooserCellEditor() {
super(new JTextField());
setClickCountToStart(CLICK_COUNT_TO_START);
// Using a JButton as the editor component
button = new JButton();
button.setBackground(Color.white);
button.setFont(button.getFont().deriveFont(Font.PLAIN));
button.setBorder(null);
// Dialog which will do the actual editing
fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
@Override
public Object getCellEditorValue() {
return file;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
file = value.toString();
SwingUtilities.invokeLater(() -> {
fileChooser.setSelectedFile(new File(file));
if (fileChooser.showOpenDialog(button) == JFileChooser.APPROVE_OPTION) {
file = fileChooser.getSelectedFile().getAbsolutePath();
}
fireEditingStopped();
});
button.setText(file);
return button;
}
}
}