/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2013 University of Dundee. All rights reserved.
*
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.agents.fsimporter.chooser;
import ij.IJ;
import ij.ImagePlus;
import info.clearthought.layout.TableLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.apache.commons.collections.CollectionUtils;
import org.openmicroscopy.shoola.agents.fsimporter.ImporterAgent;
import org.openmicroscopy.shoola.agents.fsimporter.view.Importer;
import org.openmicroscopy.shoola.agents.util.browser.DataNode;
import org.openmicroscopy.shoola.env.LookupNames;
import org.openmicroscopy.shoola.env.data.model.FileObject;
import org.openmicroscopy.shoola.env.data.model.ImportableFile;
import org.openmicroscopy.shoola.util.ui.IconManager;
import org.openmicroscopy.shoola.util.ui.MultilineHeaderSelectionRenderer;
import org.openmicroscopy.shoola.util.ui.TooltipTableHeader;
import omero.gateway.model.DatasetData;
import omero.gateway.model.ExperimenterData;
import omero.gateway.model.GroupData;
/**
* Component displaying the files to import.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* @since 3.0-Beta4
*/
class FileSelectionTable
extends JPanel
implements ActionListener
{
/** Description of the <code>Remove All</code> action.*/
private static final String TOOLTIP_BUTTON_REMOVE_ALL = "Remove all files" +
" from the queue.";
/** Description of the <code>Removel</code> action.*/
private static final String TOOLTIP_BUTTON_REMOVE = "Remove the selected" +
" files from the queue.";
/** Description of the <code>Add All</code> action.*/
private static final String TOOLTIP_BUTTON_ADD = "Add the selected files"+
"to the queue.";
/** Tooltip text for group column */
private static final String TOOLTIP_GROUP =
"The group where to import data.";
/** Tooltip text for owner column */
private static final String TOOLTIP_OWNER = "The owner of imported data";
/** Tooltip text for folder as dataset column */
private static final String TOOLTIP_FAD = "Convert the folder as dataset.";
/** Tooltip text for container column */
private static final String TOOLTIP_CONTAINER =
"The container where to import the data.";
/** Tooltip text for size column */
private static final String TOOLTIP_SIZE = "Size of File or Folder.";
/** Tooltip text for file column */
private static final String TOOLTIP_FILE = "File or Folder to import.";
/** Bound property indicating to add files to the queue. */
static final String ADD_PROPERTY = "add";
/** Bound property indicating to remove files to the queue. */
static final String REMOVE_PROPERTY = "remove";
/** Action command ID to add a field to the result table. */
private static final int ADD = 0;
/** Action command ID to remove a field from the result table. */
private static final int REMOVE = 1;
/** Action command ID to remove all fields from the result table. */
private static final int REMOVE_ALL = 2;
/** The text displayed to use the folder as container. */
private static final String FAD_TEXT = "Folder as\nDataset";
/** Indicate to select the files. */
private static final String FILE_TEXT = "File or\nFolder";
/** The text indicating the size of the file or folder. */
private static final String SIZE_TEXT = "Size";
/**
* The text displaying where to import the data to if importing
* to Project/Dataset or Screen.
*/
private static final String CONTAINER_PROJECT_TEXT =
"Project/Dataset\nor Screen";
/** The group where the files will be imported.*/
private static final String GROUP_TEXT = "Group";
/** The owner of the imported data. */
private static final String OWNER_TEXT = "Owner";
/** The button to move an item from the remaining items to current items. */
private JButton addButton;
/** The button to move an item from the current items to remaining items. */
private JButton removeButton;
/** The button to move all items to the remaining items. */
private JButton removeAllButton;
/** The table displaying the collection to files to import. */
private JTable table;
/** Reference to the model. */
private ImportDialog model;
/** The key listener added to the queue. */
private KeyAdapter keyListener;
/* the index of each column, null if the column is not shown */
private Integer fileIndex, groupIndex, ownerIndex, containerIndex, folderAsDatasetIndex, sizeIndex;
/* the headings of the columns selected for the display */
private final Vector<String> columnHeadings = new Vector<String>();
/* the tooltips for the columns selected for the display */
private final Vector<String> columnTooltips = new Vector<String>();
/** Formats the table model. */
private void formatTableModel()
{
final TableColumnModel tcm = table.getColumnModel();
int index = 0;
this.columnHeadings.clear();
this.columnTooltips.clear();
this.fileIndex = index++;
this.columnHeadings.add(FILE_TEXT);
this.columnTooltips.add(TOOLTIP_FILE);
tcm.getColumn(this.fileIndex).setCellRenderer(new FileTableRendererFileColumn());
if (model.isSingleGroup()) {
this.groupIndex = null;
} else {
this.groupIndex = index++;
this.columnHeadings.add(GROUP_TEXT);
this.columnTooltips.add(TOOLTIP_GROUP);
}
if (model.canImportAs()) {
this.ownerIndex = index++;
this.columnHeadings.add(OWNER_TEXT);
this.columnTooltips.add(TOOLTIP_OWNER);
} else {
this.ownerIndex = null;
}
this.containerIndex = index++;
this.columnHeadings.add(CONTAINER_PROJECT_TEXT);
this.columnTooltips.add(TOOLTIP_CONTAINER);
tcm.getColumn(this.containerIndex).setCellRenderer(new FileTableRendererContainerColumn());
this.folderAsDatasetIndex = index++;
this.columnHeadings.add(FAD_TEXT);
this.columnTooltips.add(TOOLTIP_FAD);
setColumnAsBoolean(tcm.getColumn(this.folderAsDatasetIndex));
this.sizeIndex = index++;
this.columnHeadings.add(SIZE_TEXT);
this.columnTooltips.add(TOOLTIP_SIZE);
table.setTableHeader(new TooltipTableHeader(tcm, columnTooltips));
final TableCellRenderer renderer = new MultilineHeaderSelectionRenderer();
while (--index >= 0) {
setHeaderRenderer(tcm, index, renderer);
}
table.getTableHeader().resizeAndRepaint();
table.getTableHeader().setReorderingAllowed(false);
}
/**
* Helper method to setup the column as a boolean column
* @param column the column to set
*/
private void setColumnAsBoolean(TableColumn column) {
column.setCellEditor(table.getDefaultEditor(Boolean.class));
column.setCellRenderer(table.getDefaultRenderer(Boolean.class));
column.setResizable(false);
}
/**
* Helper method to sets the table header with the specified index to the
* renderer provided.
* @param columnModel The <see>TableColumnModel</see> to use
* @param columnIndex The index of the column to set
* @param renderer The renderer to set
*/
private void setHeaderRenderer(TableColumnModel columnModel,
int columnIndex, TableCellRenderer renderer) {
TableColumn column = columnModel.getColumn(columnIndex);
column.setHeaderRenderer(renderer);
}
/* initialize the column headings, tool-tips, and indices thereof */
private void initColumns() {
int index = 0;
this.columnHeadings.clear();
this.columnTooltips.clear();
this.fileIndex = index++;
this.columnHeadings.add(FILE_TEXT);
this.columnTooltips.add(TOOLTIP_FILE);
if (model.isSingleGroup()) {
this.groupIndex = null;
} else {
this.groupIndex = index++;
this.columnHeadings.add(GROUP_TEXT);
this.columnTooltips.add(TOOLTIP_GROUP);
}
if (model.canImportAs()) {
this.ownerIndex = index++;
this.columnHeadings.add(OWNER_TEXT);
this.columnTooltips.add(TOOLTIP_OWNER);
} else {
this.ownerIndex = null;
}
this.containerIndex = index++;
this.columnHeadings.add(CONTAINER_PROJECT_TEXT);
this.columnTooltips.add(TOOLTIP_CONTAINER);
this.folderAsDatasetIndex = index++;
this.columnHeadings.add(FAD_TEXT);
this.columnTooltips.add(TOOLTIP_FAD);
this.sizeIndex = index++;
this.columnHeadings.add(SIZE_TEXT);
this.columnTooltips.add(TOOLTIP_SIZE);
}
/** Initializes the components composing the display. */
private void initComponents()
{
IconManager icons = IconManager.getInstance();
addButton = new JButton(icons.getIcon(IconManager.RIGHT_ARROW));
addButton.setToolTipText(TOOLTIP_BUTTON_ADD);
addButton.setEnabled(false);
removeButton = new JButton(icons.getIcon(IconManager.LEFT_ARROW));
removeButton.setToolTipText(TOOLTIP_BUTTON_REMOVE);
removeButton.setEnabled(false);
removeAllButton = new JButton(
icons.getIcon(IconManager.DOUBLE_LEFT_ARROW));
removeAllButton.setToolTipText(TOOLTIP_BUTTON_REMOVE_ALL);
removeAllButton.setEnabled(false);
addButton.setActionCommand(""+ADD);
addButton.addActionListener(this);
removeButton.setActionCommand(""+REMOVE);
removeButton.addActionListener(this);
removeAllButton.setActionCommand(""+REMOVE_ALL);
removeAllButton.addActionListener(this);
table = new JTable(new FileTableModel());
keyListener = new KeyAdapter() {
/**
* Adds the files to the import queue.
* @see KeyListener#keyPressed(KeyEvent)
*/
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (table.isFocusOwner())
removeSelectedFiles();
}
}
};
table.addKeyListener(keyListener);
formatTableModel();
}
/**
* Returns the component hosting the collection of files to import.
*
* @return See above.
*/
private JPanel buildTablePane()
{
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.PAGE_AXIS));
p.add(Box.createVerticalStrut(5));
p.add(new JScrollPane(table));
return p;
}
/** Builds and lays out the UI. */
private void buildGUI()
{
double[][] size = {{TableLayout.FILL}, {TableLayout.FILL}};
setLayout(new TableLayout(size));
add(buildTablePane(), "0, 0");
}
/** Removes the selected files from the queue. */
private void removeSelectedFiles()
{
table.removeKeyListener(keyListener);
int[] rows = table.getSelectedRows();
if (rows == null || rows.length == 0) return;
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
Vector<?> v = dtm.getDataVector();
List<Object> indexes = new ArrayList<Object>();
for (int i = 0; i < table.getRowCount(); i++) {
if (table.isRowSelected(i))
indexes.add(v.get(i));
}
v.removeAll(indexes);
dtm.setDataVector(v, this.columnHeadings);
table.clearSelection();
formatTableModel();
table.repaint();
table.addKeyListener(keyListener);
int n = table.getRowCount();
firePropertyChange(REMOVE_PROPERTY, n-1, n);
enabledControl(table.getRowCount() > 0);
model.onSelectionChanged();
}
/**
* Returns <code>true</code> if the file can be added to the queue again,
* <code>false</code> otherwise.
*
* @param queue The list of files already in the queue.
* @param f The file to check.
* @param gID The id of the group to import the image into.
* @param userID The id of the user.
* @return See above.
*/
private boolean allowAddToQueue(List<FileElement> queue, FileObject f, long gID,
long userID)
{
if (f == null) return false;
if (queue == null) return true;
Object o = f.getFile();
if (f.isNewImage()) {
return true;
}
File file = f.getTrueFile();
//check if file is null
if (file == null) return false;
Iterator<FileElement> i = queue.iterator();
FileElement fe;
String name = file.getAbsolutePath();
while (i.hasNext()) {
fe = i.next();
if (fe.getFile().getAbsolutePath().equals(name) &&
fe.getGroup().getId() == gID &&
fe.getUser().getId() == userID) {
o = fe.getFile().getFile();
if (o instanceof ImagePlus && f.getFile() instanceof ImagePlus) {
fe.getFile().addAssociatedFile(new FileObject(f.getFile()));
}
return false;
}
}
return true;
}
/**
* Sets the enabled flag of the buttons.
*
* @param value The value to set.
*/
private void enabledControl(boolean value)
{
removeButton.setEnabled(value);
removeAllButton.setEnabled(value);
}
/**
* Creates a new instance.
*
* @param model The model.
*/
FileSelectionTable(ImportDialog model)
{
if (model == null)
throw new IllegalArgumentException("No model.");
this.model = model;
initColumns();
initComponents();
buildGUI();
}
/**
* Builds and lays out the controls.
*
* @return See above.
*/
JPanel buildControls()
{
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
if (ImporterAgent.runAsPlugin() != LookupNames.IMAGE_J_IMPORT) {
p.add(addButton);
p.add(Box.createVerticalStrut(5));
}
p.add(removeButton);
p.add(Box.createVerticalStrut(5));
p.add(removeAllButton);
return p;
}
/**
* Returns <code>true</code> if there are files to import,
* <code>false</code> otherwise.
*
* @return See above.
*/
boolean hasFilesToImport() { return table.getRowCount() > 0; }
/**
* Returns the collection of files to import.
*
* @return See above.
*/
List<ImportableFile> getFilesToImport()
{
List<ImportableFile> files = new ArrayList<ImportableFile>();
int n = table.getRowCount();
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
FileElement element;
FileObject file;
ImportableFile importable;
boolean isFolderDataset;
DataNodeElement dne;
DatasetData dataset;
for (int i = 0; i < n; i++) {
element = (FileElement) dtm.getValueAt(i, this.fileIndex);
file = element.getFile();
dne = (DataNodeElement) dtm.getValueAt(i, this.containerIndex);
isFolderDataset = (Boolean) dtm.getValueAt(i, this.folderAsDatasetIndex);
importable = new ImportableFile(file, isFolderDataset);
dataset = dne.getLocation();
if (isFolderDataset) dataset = null;
importable.setLocation(dne.getParent(), dataset);
importable.setRefNode(dne.getRefNode());
importable.setGroup(element.getGroup());
importable.setUser(element.getUser());
files.add(importable);
}
return files;
}
/**
* Resets the components.
*
* @param value The value to set.
*/
void reset(boolean value)
{
allowAddition(value);
initColumns();
table.setModel(new FileTableModel());
formatTableModel();
}
/**
* Sets the enable flag of the {@link #addButton}.
*
* @param value The value to set.
*/
void allowAddition(boolean value)
{
addButton.setEnabled(value);
}
/** Removes all the files from the queue. */
void removeAllFiles()
{
int n = table.getRowCount();
if (n == 0) return;
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
dtm.getDataVector().clear();
table.clearSelection();
formatTableModel();
table.repaint();
firePropertyChange(REMOVE_PROPERTY, -1, 0);
enabledControl(false);
model.onSelectionChanged();
}
/**
* Adds the collection of files to the queue.
*
* @param files The files to add.
* @param settings The import settings.
*/
void addFiles(List<FileObject> files, ImportLocationSettings settings)
{
if (CollectionUtils.isEmpty(files)) return;
boolean fad = settings.isParentFolderAsDataset();
GroupData group = settings.getImportGroup();
ExperimenterData user = settings.getImportUser();
enabledControl(true);
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
//Check if the file has already
List<FileElement> inQueue = new ArrayList<FileElement>();
FileElement element;
for (int i = 0; i < table.getRowCount(); i++) {
element = (FileElement) dtm.getValueAt(i, this.fileIndex);
inQueue.add(element);
}
Iterator<FileObject> i = files.iterator();
DataNode node = settings.getImportLocation();
node.setParent(settings.getParentImportLocation());
String value = null;
boolean v;
long gID = group.getId();
FileObject f;
while (i.hasNext()) {
f = i.next();
if (allowAddToQueue(inQueue, f, gID, user.getId())) {
element = new FileElement(f, model.getType(), group, user);
element.setName(f.getName());
inQueue.add(element);
value = null;
v = false;
value = f.getFolderAsContainerName();
if (f.isDirectory()) {
v = fad;
if (model.getType() == Importer.SCREEN_TYPE) {
value = null;
}
} else {
if (fad) {
v = true;
element.setToggleContainer(v);
}
}
final Vector<Object> row = new Vector<Object>();
row.setSize(this.columnHeadings.size());
if (this.fileIndex != null) {
row.set(this.fileIndex, element);
}
if (this.groupIndex != null) {
row.set(this.groupIndex, group.getName());
}
if (this.ownerIndex != null) {
row.set(this.ownerIndex, user.getUserName());
}
if (this.containerIndex != null) {
row.set(this.containerIndex, new DataNodeElement(node, value));
}
if (this.folderAsDatasetIndex != null) {
row.set(this.folderAsDatasetIndex, v);
}
if (this.sizeIndex != null) {
row.set(this.sizeIndex, element.getFileLengthAsString());
}
dtm.addRow(row);
}
}
model.onSelectionChanged();
}
/**
* Returns the size of the files to import.
*
* @return See above.
*/
long getSizeFilesInQueue()
{
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
FileElement element;
long size = 0;
for (int i = 0; i < table.getRowCount(); i++) {
element = (FileElement) dtm.getValueAt(i, this.fileIndex);
size += element.getFileLength();
}
return size;
}
/**
* Marks the folder as a dataset.
*
* @param fad Pass <code>true</code> to mark the folder as a dataset,
* <code>false</code> otherwise.
*/
void markFolderAsDataset(boolean fad)
{
int n = table.getRowCount();
if (n == 0) return;
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
FileElement element;
for (int i = 0; i < n; i++) {
element = (FileElement) dtm.getValueAt(i, this.fileIndex);
if (element.isDirectory())
dtm.setValueAt(fad, i, this.folderAsDatasetIndex);
}
}
/** Resets the names of all selected files. */
void resetFilesName()
{
int n = table.getRowCount();
if (n == 0) return;
DefaultTableModel model = (DefaultTableModel) table.getModel();
FileElement element;
for (int i = 0; i < n; i++) {
element = (FileElement) model.getValueAt(i, this.fileIndex);
element.setName(element.getFile().getAbsolutePath());
}
table.repaint();
}
/** Applies the partial names to all the files. */
void applyToAll()
{
int n = table.getRowCount();
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
FileElement element;
for (int i = 0; i < n; i++) {
element = (FileElement) dtm.getValueAt(i, this.fileIndex);
if (!element.isDirectory()) {
element.setName(model.getDisplayedFileName(
element.getFile().getAbsolutePath()));
}
}
table.repaint();
}
/**
* Adds or removes files from the import queue.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent evt)
{
int index = Integer.parseInt(evt.getActionCommand());
switch (index) {
case ADD:
firePropertyChange(ADD_PROPERTY, false, true);
break;
case REMOVE:
removeSelectedFiles();
break;
case REMOVE_ALL:
removeAllFiles();
}
}
/** Inner class so that some cells cannot be edited. */
class FileTableModel
extends DefaultTableModel
{
/**
* Creates a new instance.
*/
FileTableModel()
{
super(null, columnHeadings);
}
/**
* Overridden so that some cells cannot be edited.
* @see DefaultTableModel#isCellEditable(int, int)
*/
public boolean isCellEditable(int row, int column)
{
return false;
}
/**
* Overridden to set the name of the image to save.
* @see DefaultTableModel#setValueAt(Object, int, int)
*/
public void setValueAt(Object value, int row, int col)
{
if (value instanceof Boolean) {
if (col == folderAsDatasetIndex) {
DataNodeElement element = (DataNodeElement) getValueAt(row,
containerIndex);
FileElement f = (FileElement) getValueAt(row, fileIndex);
if (f.isDirectory() || (!f.isDirectory() &&
f.isToggleContainer())) {
boolean b = (Boolean) value;
if (b) element.setName(f.getName());
else element.setName(null);
}
}
super.setValueAt(value, row, col);
}
fireTableCellUpdated(row, col);
}
}
}