/* * 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; } } }