/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy 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.
*
* Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.gui.preferences;
import icy.gui.component.IcyTable;
import icy.gui.component.IcyTextField;
import icy.gui.component.IcyTextField.TextChangeListener;
import icy.gui.plugin.PluginDetailPanel;
import icy.gui.util.ComponentUtil;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.preferences.RepositoryPreferences;
import icy.preferences.RepositoryPreferences.RepositoryInfo;
import icy.resource.ResourceUtil;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
/**
* @author Stephane
*/
public abstract class PluginListPreferencePanel extends PreferencePanel implements TextChangeListener,
ListSelectionListener
{
/**
*
*/
private static final long serialVersionUID = -2718763355377652489L;
static final String[] columnNames = {"", "Name", "Version", "State", "Enabled"};
static final String[] columnIds = {"Icon", "Name", "Version", "State", "Enabled"};
List<PluginDescriptor> plugins;
/**
* gui
*/
final AbstractTableModel tableModel;
final JTable table;
final JComboBox repository;
final JPanel repositoryPanel;
final IcyTextField filter;
final JButton refreshButton;
final JButton documentationButton;
final JButton detailButton;
final JButton action1Button;
final JButton action2Button;
private final Runnable buttonsStateUpdater;
private final Runnable tableDataRefresher;
private final Runnable pluginsListRefresher;
private final Runnable repositoriesUpdater;
final ActionListener repositoryActionListener;
PluginListPreferencePanel(PreferenceFrame parent, String nodeName, String parentName)
{
super(parent, nodeName, parentName);
plugins = new ArrayList<PluginDescriptor>();
buttonsStateUpdater = new Runnable()
{
@Override
public void run()
{
// need to be done on EDT
ThreadUtil.invokeNow(new Runnable()
{
@Override
public void run()
{
updateButtonsStateInternal();
}
});
}
};
tableDataRefresher = new Runnable()
{
@Override
public void run()
{
// need to be done on EDT
ThreadUtil.invokeNow(new Runnable()
{
@Override
public void run()
{
refreshTableDataInternal();
}
});
}
};
pluginsListRefresher = new Runnable()
{
@Override
public void run()
{
refreshPluginsInternal();
}
};
repositoriesUpdater = new Runnable()
{
@Override
public void run()
{
// need to be done on EDT
ThreadUtil.invokeNow(new Runnable()
{
@Override
public void run()
{
updateRepositoriesInternal();
}
});
}
};
repositoryActionListener = new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
repositoryChanged();
}
};
repository = new JComboBox();
repository.setToolTipText("Select a repository");
repository.addActionListener(repositoryActionListener);
repositoryPanel = new JPanel();
repositoryPanel.setLayout(new BoxLayout(repositoryPanel, BoxLayout.PAGE_AXIS));
repositoryPanel.setVisible(false);
final JPanel internalRepPanel = new JPanel();
internalRepPanel.setLayout(new BoxLayout(internalRepPanel, BoxLayout.LINE_AXIS));
internalRepPanel.add(new JLabel("Repository :"));
internalRepPanel.add(Box.createHorizontalStrut(8));
internalRepPanel.add(repository);
internalRepPanel.add(Box.createHorizontalGlue());
repositoryPanel.add(internalRepPanel);
repositoryPanel.add(Box.createVerticalStrut(8));
// need filter before load()
filter = new IcyTextField();
filter.addTextChangeListener(this);
// build buttons panel
final Dimension buttonsDim = new Dimension(100, 24);
refreshButton = new JButton("Reload list");
refreshButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
reloadPlugins();
}
});
ComponentUtil.setFixedSize(refreshButton, buttonsDim);
documentationButton = new JButton("Online doc");
documentationButton.setToolTipText("Open the online documentation");
documentationButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final List<PluginDescriptor> selectedPlugins = getSelectedPlugins();
// open plugin web page
if (selectedPlugins.size() == 1)
NetworkUtil.openBrowser(selectedPlugins.get(0).getWeb());
}
});
ComponentUtil.setFixedSize(documentationButton, buttonsDim);
detailButton = new JButton("Show detail");
detailButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
final List<PluginDescriptor> selectedPlugins = getSelectedPlugins();
// open the detail
if (selectedPlugins.size() == 1)
new PluginDetailPanel(selectedPlugins.get(0));
}
});
ComponentUtil.setFixedSize(detailButton, buttonsDim);
action1Button = new JButton("null");
action1Button.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
doAction1();
}
});
action1Button.setVisible(false);
ComponentUtil.setFixedSize(action1Button, buttonsDim);
action2Button = new JButton("null");
action2Button.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
doAction2();
}
});
action2Button.setVisible(false);
ComponentUtil.setFixedSize(action2Button, buttonsDim);
final JPanel buttonsPanel = new JPanel();
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(4, 8, 8, 8));
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.PAGE_AXIS));
buttonsPanel.add(refreshButton);
buttonsPanel.add(Box.createVerticalStrut(34));
buttonsPanel.add(documentationButton);
buttonsPanel.add(Box.createVerticalStrut(8));
buttonsPanel.add(detailButton);
buttonsPanel.add(Box.createVerticalStrut(8));
buttonsPanel.add(action1Button);
buttonsPanel.add(Box.createVerticalStrut(8));
buttonsPanel.add(action2Button);
buttonsPanel.add(Box.createVerticalStrut(8));
buttonsPanel.add(Box.createVerticalGlue());
// build table
tableModel = new AbstractTableModel()
{
/**
*
*/
private static final long serialVersionUID = -8573364273165723214L;
@Override
public int getColumnCount()
{
return columnNames.length;
}
@Override
public String getColumnName(int column)
{
return columnNames[column];
}
@Override
public int getRowCount()
{
return plugins.size();
}
@Override
public Object getValueAt(int row, int column)
{
if (row < plugins.size())
{
final PluginDescriptor plugin = plugins.get(row);
switch (column)
{
case 0:
if (plugin.isIconLoaded())
return ResourceUtil.scaleIcon(plugin.getIcon(), 32);
loadIconAsync(plugin);
return ResourceUtil.scaleIcon(PluginDescriptor.DEFAULT_ICON, 32);
case 1:
return plugin.getName();
case 2:
return plugin.getVersion().toString();
case 3:
return getStateValue(plugin);
case 4:
return Boolean.valueOf(isActive(plugin));
}
}
return "";
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
if (rowIndex < plugins.size())
{
final PluginDescriptor plugin = plugins.get(rowIndex);
if (columnIndex == 4)
{
if (aValue instanceof Boolean)
setActive(plugin, ((Boolean) aValue).booleanValue());
}
}
}
@Override
public boolean isCellEditable(int row, int column)
{
return (column == 4);
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
switch (columnIndex)
{
case 0:
return ImageIcon.class;
case 4:
return Boolean.class;
default:
return String.class;
}
}
};
table = new IcyTable(tableModel);
final TableColumnModel colModel = table.getColumnModel();
TableColumn col;
// columns setting
col = colModel.getColumn(0);
col.setIdentifier(columnIds[0]);
col.setMinWidth(32);
col.setPreferredWidth(32);
col.setMaxWidth(32);
col = colModel.getColumn(1);
col.setIdentifier(columnIds[1]);
col.setMinWidth(120);
col.setPreferredWidth(200);
col.setMaxWidth(500);
col = colModel.getColumn(2);
col.setIdentifier(columnIds[2]);
col.setMinWidth(60);
col.setPreferredWidth(60);
col.setMaxWidth(60);
col = colModel.getColumn(3);
col.setIdentifier(columnIds[3]);
col.setMinWidth(70);
col.setPreferredWidth(90);
col.setMaxWidth(120);
col = colModel.getColumn(4);
col.setIdentifier(columnIds[4]);
col.setMinWidth(60);
col.setPreferredWidth(60);
col.setMaxWidth(60);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.getSelectionModel().addListSelectionListener(this);
table.setRowHeight(32);
table.setColumnSelectionAllowed(false);
table.setRowSelectionAllowed(true);
table.setShowVerticalLines(false);
table.setAutoCreateRowSorter(true);
// sort on name by default
table.getRowSorter().toggleSortOrder(1);
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
table.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent me)
{
if (!me.isConsumed())
{
if (me.getClickCount() == 2)
{
// show detail
detailButton.doClick();
me.consume();
}
}
}
});
final JPanel tableTopPanel = new JPanel();
tableTopPanel.setLayout(new BoxLayout(tableTopPanel, BoxLayout.PAGE_AXIS));
tableTopPanel.add(Box.createVerticalStrut(2));
tableTopPanel.add(repositoryPanel);
tableTopPanel.add(filter);
tableTopPanel.add(Box.createVerticalStrut(8));
tableTopPanel.add(table.getTableHeader());
final JPanel tablePanel = new JPanel();
tablePanel.setLayout(new BorderLayout());
tablePanel.add(tableTopPanel, BorderLayout.NORTH);
tablePanel.add(new JScrollPane(table), BorderLayout.CENTER);
mainPanel.setLayout(new BorderLayout());
mainPanel.add(tablePanel, BorderLayout.CENTER);
mainPanel.add(buttonsPanel, BorderLayout.EAST);
mainPanel.validate();
}
protected void loadIconAsync(final PluginDescriptor plugin)
{
ThreadUtil.bgRun(new Runnable()
{
@Override
public void run()
{
// icon correctly loaded ?
if (plugin.loadIcon())
refreshTableData();
}
});
}
@Override
protected void closed()
{
super.closed();
// do not retains plugins when frame is closed
plugins.clear();
}
private List<PluginDescriptor> filterList(List<PluginDescriptor> list, String filter)
{
final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
final boolean empty = StringUtil.isEmpty(filter, true);
final String filterUp;
if (!empty)
filterUp = filter.toUpperCase();
else
filterUp = "";
for (PluginDescriptor plugin : list)
{
final String classname = plugin.getClassName().toUpperCase();
final String name = plugin.getName().toUpperCase();
final String desc = plugin.getDescription().toUpperCase();
// search in name and description
if (empty || (classname.indexOf(filterUp) != -1) || (name.indexOf(filterUp) != -1)
|| (desc.indexOf(filterUp) != -1))
result.add(plugin);
}
return result;
}
protected boolean isActive(PluginDescriptor plugin)
{
return false;
}
protected void setActive(PluginDescriptor plugin, boolean value)
{
}
protected abstract void doAction1();
protected abstract void doAction2();
protected abstract void repositoryChanged();
protected abstract void reloadPlugins();
protected abstract String getStateValue(PluginDescriptor plugin);
protected abstract List<PluginDescriptor> getPlugins();
protected int getPluginTableIndex(int rowIndex)
{
if (rowIndex == -1)
return rowIndex;
try
{
return table.convertRowIndexToView(rowIndex);
}
catch (IndexOutOfBoundsException e)
{
return -1;
}
}
protected int getPluginIndex(PluginDescriptor plugin)
{
return plugins.indexOf(plugin);
}
protected int getPluginModelIndex(PluginDescriptor plugin)
{
return getPluginIndex(plugin);
}
protected int getPluginTableIndex(PluginDescriptor plugin)
{
return getPluginTableIndex(getPluginModelIndex(plugin));
}
protected int getPluginIndex(String pluginClassName)
{
for (int i = 0; i < plugins.size(); i++)
{
final PluginDescriptor plugin = plugins.get(i);
if (plugin.getClassName().equals(pluginClassName))
return i;
}
return -1;
}
protected int getPluginModelIndex(String pluginClassName)
{
return getPluginIndex(pluginClassName);
}
protected int getPluginTableIndex(String pluginClassName)
{
return getPluginTableIndex(getPluginModelIndex(pluginClassName));
}
List<PluginDescriptor> getSelectedPlugins()
{
final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
final int[] rows = table.getSelectedRows();
if (rows.length == 0)
return result;
final List<PluginDescriptor> cachedPlugins = plugins;
for (int i = 0; i < rows.length; i++)
{
try
{
final int index = table.convertRowIndexToModel(rows[i]);
if (index < cachedPlugins.size())
result.add(cachedPlugins.get(index));
}
catch (IndexOutOfBoundsException e)
{
// ignore as async process can cause it
}
}
return result;
}
/**
* Select the specified list of ROI in the ROI Table
*/
void setSelectedPlugins(HashSet<PluginDescriptor> newSelected)
{
final List<PluginDescriptor> modelPlugins = plugins;
final ListSelectionModel selectionModel = table.getSelectionModel();
// start selection change
selectionModel.setValueIsAdjusting(true);
try
{
// start by clearing selection
selectionModel.clearSelection();
for (int i = 0; i < modelPlugins.size(); i++)
{
final PluginDescriptor plugin = modelPlugins.get(i);
// HashSet provide fast "contains"
if (newSelected.contains(plugin))
{
try
{
// convert model index to view index
final int ind = table.convertRowIndexToView(i);
if (ind != -1)
selectionModel.addSelectionInterval(ind, ind);
}
catch (IndexOutOfBoundsException e)
{
// ignore
}
}
}
}
finally
{
// end selection change
selectionModel.setValueIsAdjusting(false);
}
}
protected void refreshPluginsInternal()
{
plugins = filterList(getPlugins(), filter.getText());
// refresh table data
refreshTableData();
}
protected final void refreshPlugins()
{
ThreadUtil.runSingle(pluginsListRefresher);
}
protected void updateButtonsStateInternal()
{
final List<PluginDescriptor> selectedPlugins = getSelectedPlugins();
final boolean singleSelection = (selectedPlugins.size() == 1);
final PluginDescriptor singlePlugin = singleSelection ? selectedPlugins.get(0) : null;
detailButton.setEnabled(singleSelection);
documentationButton.setEnabled(singleSelection && !StringUtil.isEmpty(singlePlugin.getWeb()));
}
protected final void updateButtonsState()
{
ThreadUtil.runSingle(buttonsStateUpdater);
}
protected void updateRepositoriesInternal()
{
// final RepositoryPreferencePanel panel = (RepositoryPreferencePanel)
// getPreferencePanel(RepositoryPreferencePanel.class);
// // refresh repositories list (use list from GUI)
// final ArrayList<RepositoryInfo> repositeries = panel.repositories;
// refresh repositories list
final List<RepositoryInfo> repositeries = RepositoryPreferences.getRepositeries();
final RepositoryInfo savedRepository = (RepositoryInfo) repository.getSelectedItem();
// needed to disable events during update time
repository.removeActionListener(repositoryActionListener);
repository.removeAllItems();
for (RepositoryInfo repos : repositeries)
if (repos.isEnabled())
repository.addItem(repos);
repository.addActionListener(repositoryActionListener);
boolean selected = false;
// try to set back the old selected repository
if (savedRepository != null)
{
final String repositoryName = savedRepository.getName();
for (int ind = 0; ind < repository.getItemCount(); ind++)
{
final RepositoryInfo repo = (RepositoryInfo) repository.getItemAt(ind);
if ((repo != null) && (repo.getName().equals(repositoryName)))
{
repository.setSelectedIndex(ind);
selected = true;
break;
}
}
}
// manually launch the action
if (!selected)
repository.setSelectedIndex((repository.getItemCount() > 0) ? 0 : -1);
// avoid automatic minimum size here
repository.setMinimumSize(new Dimension(48, 18));
}
protected final void updateRepositories()
{
ThreadUtil.runSingle(repositoriesUpdater);
}
protected void refreshTableDataInternal()
{
final List<PluginDescriptor> plugins = getSelectedPlugins();
tableModel.fireTableDataChanged();
// restore previous selected plugins if possible
setSelectedPlugins(new HashSet<PluginDescriptor>(plugins));
// update button state
buttonsStateUpdater.run();
}
protected final void refreshTableData()
{
ThreadUtil.runSingle(tableDataRefresher);
}
protected void pluginsChanged()
{
refreshPlugins();
}
@Override
protected void load()
{
}
@Override
protected void save()
{
// reload repositories as some parameter as beta flag can have changed
updateRepositories();
}
@Override
public void textChanged(IcyTextField source, boolean validate)
{
pluginsChanged();
}
@Override
public void valueChanged(ListSelectionEvent e)
{
final int selected = table.getSelectedRow();
if (!e.getValueIsAdjusting() && (selected != -1))
{
final int fi = e.getFirstIndex();
final int li = e.getLastIndex();
if ((fi == -1) || ((fi <= selected) && (li >= selected)))
updateButtonsState();
}
}
}