/*
* 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.inspector;
import icy.action.CanvasActions;
import icy.canvas.CanvasLayerEvent;
import icy.canvas.CanvasLayerListener;
import icy.canvas.IcyCanvas;
import icy.canvas.Layer;
import icy.gui.component.IcyTextField;
import icy.gui.component.IcyTextField.TextChangeListener;
import icy.gui.component.editor.VisibleCellEditor;
import icy.gui.component.renderer.VisibleCellRenderer;
import icy.gui.main.ActiveViewerListener;
import icy.gui.viewer.Viewer;
import icy.gui.viewer.ViewerEvent;
import icy.gui.viewer.ViewerEvent.ViewerEventType;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;
import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.table.ColumnControlButton;
import org.jdesktop.swingx.table.TableColumnExt;
/**
* @author Stephane
*/
public class LayersPanel extends JPanel implements ActiveViewerListener, CanvasLayerListener, TextChangeListener,
ListSelectionListener
{
private class CanvasRefresher implements Runnable
{
IcyCanvas newCanvas;
public CanvasRefresher()
{
super();
}
@Override
public void run()
{
final IcyCanvas c = newCanvas;
// change canvas
if (canvas != c)
{
if (canvas != null)
canvas.removeLayerListener(LayersPanel.this);
canvas = c;
if (canvas != null)
canvas.addLayerListener(LayersPanel.this);
}
refreshLayersInternal();
}
}
/**
*
*/
private static final long serialVersionUID = 4550426171735455449L;
static final String[] columnNames = {"Name", ""};
List<Layer> layers;
IcyCanvas canvas;
// GUI
AbstractTableModel tableModel;
ListSelectionModel tableSelectionModel;
JXTable table;
IcyTextField nameFilter;
LayerControlPanel controlPanel;
// internals
boolean isSelectionAdjusting;
boolean isLayerEditing;
boolean isLayerPropertiesAdjusting;
final Runnable layersRefresher;
final Runnable tableDataRefresher;
final Runnable controlPanelRefresher;
final CanvasRefresher canvasRefresher;
public LayersPanel()
{
super();
layers = new ArrayList<Layer>();
canvas = null;
isSelectionAdjusting = false;
isLayerEditing = false;
isLayerPropertiesAdjusting = false;
layersRefresher = new Runnable()
{
@Override
public void run()
{
refreshLayersInternal();
}
};
tableDataRefresher = new Runnable()
{
@Override
public void run()
{
refreshTableData();
}
};
controlPanelRefresher = new Runnable()
{
@Override
public void run()
{
controlPanel.refresh();
}
};
canvasRefresher = new CanvasRefresher();
// build GUI
initialize();
// 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 layers.size();
}
@Override
public Object getValueAt(int row, int column)
{
// safe
if (row >= layers.size())
return null;
final Layer layer = layers.get(row);
switch (column)
{
case 0:
// layer name
return layer.getName();
case 1:
// layer visibility
return Boolean.valueOf(layer.isVisible());
default:
return "";
}
}
@Override
public void setValueAt(Object value, int row, int column)
{
// safe
if (row >= layers.size())
return;
isLayerEditing = true;
try
{
final Layer layer = layers.get(row);
switch (column)
{
case 0:
layer.setName((String) value);
break;
case 1:
// layer visibility
layer.setVisible(((Boolean) value).booleanValue());
break;
}
}
finally
{
isLayerEditing = false;
}
}
@Override
public boolean isCellEditable(int row, int column)
{
// safe
if (row >= layers.size())
return false;
final boolean editable;
// name field ?
if (column == 0)
{
final Layer layer = layers.get(row);
editable = (layer != null) ? !layer.isReadOnly() : false;
}
else
editable = true;
return editable;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
switch (columnIndex)
{
default:
case 0:
// layer name
return String.class;
case 1:
// layer visibility
return Boolean.class;
}
}
};
// set table model
table.setModel(tableModel);
// alternate highlight
table.setHighlighters(HighlighterFactory.createSimpleStriping());
// disable extra actions from column control
((ColumnControlButton) table.getColumnControl()).setAdditionalActionsVisible(false);
// remove the internal find command (we have our own filter)
table.getActionMap().remove("find");
TableColumnExt col;
// columns setting - name
col = table.getColumnExt(0);
col.setPreferredWidth(140);
col.setToolTipText("Layer name (double click in a cell to edit)");
// columns setting - visible
col = table.getColumnExt(1);
col.setPreferredWidth(20);
col.setMinWidth(20);
col.setMaxWidth(20);
col.setCellEditor(new VisibleCellEditor(18));
col.setCellRenderer(new VisibleCellRenderer(18));
col.setToolTipText("Make the layer visible or not");
col.setResizable(false);
// table selection model
tableSelectionModel = table.getSelectionModel();
tableSelectionModel.addListSelectionListener(this);
tableSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// create shortcuts
buildActionMap();
// and refresh layers
refreshLayers();
}
private void initialize()
{
nameFilter = new IcyTextField();
nameFilter.setToolTipText("Enter a string sequence to filter Layer on name");
nameFilter.addTextChangeListener(this);
table = new JXTable();
table.setAutoStartEditOnKeyStroke(false);
table.setRowHeight(24);
table.setShowVerticalLines(false);
table.setColumnControlVisible(true);
table.setColumnSelectionAllowed(false);
table.setRowSelectionAllowed(true);
table.setAutoCreateRowSorter(true);
controlPanel = new LayerControlPanel(this);
setLayout(new BorderLayout(0, 0));
add(nameFilter, BorderLayout.NORTH);
add(new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER);
add(controlPanel, BorderLayout.SOUTH);
validate();
}
void buildActionMap()
{
final InputMap imap = table.getInputMap(JComponent.WHEN_FOCUSED);
final ActionMap amap = table.getActionMap();
imap.put(CanvasActions.unselectAction.getKeyStroke(), CanvasActions.unselectAction.getName());
imap.put(CanvasActions.deleteLayersAction.getKeyStroke(), CanvasActions.deleteLayersAction.getName());
// also allow backspace key for delete operation here
imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), CanvasActions.deleteLayersAction.getName());
// disable search feature (we have our own filter)
amap.remove("find");
amap.put(CanvasActions.unselectAction.getName(), CanvasActions.unselectAction);
amap.put(CanvasActions.deleteLayersAction.getName(), CanvasActions.deleteLayersAction);
}
public void setNameFilter(String name)
{
nameFilter.setText(name);
}
/**
* refresh Layer list (and refresh table data according)
*/
protected void refreshLayers()
{
ThreadUtil.runSingle(layersRefresher);
}
/**
* refresh layer list (internal)
*/
void refreshLayersInternal()
{
if (canvas != null)
layers = filterList(canvas.getLayers(false), nameFilter.getText());
else
layers.clear();
// refresh table data
ThreadUtil.runSingle(tableDataRefresher);
}
/**
* Return index of specified Layer in the Layer list
*/
protected int getLayerIndex(Layer layer)
{
return layers.indexOf(layer);
}
/**
* Return index of specified Layer in the model
*/
protected int getLayerModelIndex(Layer layer)
{
return getLayerIndex(layer);
}
/**
* Return index of specified Layer in the table
*/
protected int getLayerTableIndex(Layer layer)
{
final int ind = getLayerModelIndex(layer);
if (ind == -1)
return ind;
try
{
return table.convertRowIndexToView(ind);
}
catch (IndexOutOfBoundsException e)
{
return -1;
}
}
// public Layer getFirstSelectedRoi()
// {
// int index = table.getSelectedRow();
//
// if (index != -1)
// {
// try
// {
// index = table.convertRowIndexToModel(index);
// }
// catch (IndexOutOfBoundsException e)
// {
// // ignore
// }
//
// if ((index >= 0) || (index < layers.size()))
// return layers.get(index);
// }
//
// return null;
// }
public ArrayList<Layer> getSelectedLayers()
{
final ArrayList<Layer> result = new ArrayList<Layer>();
for (int rowIndex : table.getSelectedRows())
{
int index = -1;
if (rowIndex != -1)
{
try
{
index = table.convertRowIndexToModel(rowIndex);
}
catch (IndexOutOfBoundsException e)
{
// ignore
}
}
if ((index >= 0) && (index < layers.size()))
result.add(layers.get(index));
}
return result;
}
public void clearSelected()
{
setSelectedLayersInternal(new ArrayList<Layer>());
}
void setSelectedLayersInternal(List<Layer> newSelected)
{
isSelectionAdjusting = true;
try
{
table.clearSelection();
if (newSelected != null)
{
for (Layer layer : newSelected)
{
final int index = getLayerTableIndex(layer);
if (index > -1)
tableSelectionModel.addSelectionInterval(index, index);
}
}
}
finally
{
isSelectionAdjusting = false;
}
// notify selection changed
selectionChanged();
}
List<Layer> filterList(List<Layer> list, String nameFilterText)
{
final List<Layer> result = new ArrayList<Layer>();
final boolean nameEmpty = StringUtil.isEmpty(nameFilterText, true);
final String nameFilterUp;
if (!nameEmpty)
nameFilterUp = nameFilterText.trim().toLowerCase();
else
nameFilterUp = "";
for (Layer layer : list)
{
// search in name and type
if (nameEmpty || (layer.getName().toLowerCase().indexOf(nameFilterUp) != -1))
result.add(layer);
}
return result;
}
protected void refreshTableData()
{
final List<Layer> save = getSelectedLayers();
// need to be done on EDT
ThreadUtil.invokeNow(new Runnable()
{
@Override
public void run()
{
isSelectionAdjusting = true;
try
{
tableModel.fireTableDataChanged();
}
finally
{
isSelectionAdjusting = false;
}
setSelectedLayersInternal(save);
}
});
}
// protected void refreshTableRow(final Layer layer)
// {
// isSelectionAdjusting = true;
// try
// {
// final int rowIndex = getLayerModelIndex(layer);
//
// tableModel.fireTableRowsUpdated(rowIndex, rowIndex);
// }
// finally
// {
// isSelectionAdjusting = false;
// }
//
// // restore selected layer
// if (sequence != null)
// setSelectedLayersInternal(sequence.getSelectedROIs());
// else
// setSelectedLayersInternal(null);
//
// // refresh control panel
// refreshControlPanel();
// }
/**
* Called when selection changed
*/
protected void selectionChanged()
{
// refresh control panel
ThreadUtil.runSingle(controlPanelRefresher);
}
@Override
public void textChanged(IcyTextField source, boolean validate)
{
if (source == nameFilter)
refreshLayers();
}
@Override
public void valueChanged(ListSelectionEvent e)
{
// internal change --> ignore
if (isSelectionAdjusting || e.getValueIsAdjusting())
return;
selectionChanged();
}
@Override
public void viewerActivated(Viewer viewer)
{
if (viewer != null)
canvasRefresher.newCanvas = viewer.getCanvas();
else
canvasRefresher.newCanvas = null;
ThreadUtil.runSingle(canvasRefresher);
}
@Override
public void viewerDeactivated(Viewer viewer)
{
// nothing here
}
@Override
public void activeViewerChanged(ViewerEvent event)
{
if (event.getType() == ViewerEventType.CANVAS_CHANGED)
{
canvasRefresher.newCanvas = event.getSource().getCanvas();
ThreadUtil.runSingle(canvasRefresher);
}
}
@Override
public void canvasLayerChanged(CanvasLayerEvent event)
{
// refresh layer from externals changes
if (isLayerEditing)
return;
switch (event.getType())
{
case ADDED:
case REMOVED:
refreshLayers();
break;
case CHANGED:
final String property = event.getProperty();
if (Layer.PROPERTY_NAME.equals(property) || Layer.PROPERTY_OPACITY.equals(property)
|| Layer.PROPERTY_VISIBLE.equals(property))
// refresh table data
ThreadUtil.runSingle(tableDataRefresher);
break;
}
}
}