/* MontageTableTransferHandler.java created 2008-01-04
*
*/
package org.signalml.app.view.montage.dnd;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.Arrays;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import org.apache.log4j.Logger;
import org.signalml.app.view.montage.MontageTable;
import org.signalml.domain.montage.Montage;
import org.signalml.domain.montage.MontageChannel;
import org.signalml.domain.montage.SourceChannel;
/**
* The {@link TransferHandler} for {@link MontageTable}.
* If {@link MontageChannel montage} (actually the {@link MontageChannelIndices})
* or {@link SourceChannel source} (actually the {@link SourceChannelIndices})
* channels are dragged and dropped on the element to which this transfer
* handler is assigned, then the operation is performed according to the type
* of the transferred object:
* <ul>
* <li>If transferred are {@link SourceChannelIndices}:
* <ul>
* <li>checks if the collection is of proper type and not empty,</li>
* <li>{@link Montage#addMontageChannels(int[], int) adds} the new
* {@link MontageChannel montage channels} (created on the basis of the
* {@link SourceChannel source channels} from the collection) to the
* {@link Montage montage} that is the model for the montage table.</li>
* </ul></li>
* <li>If transferred are {@link MontageChannelIndices}:
* <ul>
* <li>checks if the collection is of proper type and not empty,</li>
* <li>checks if the collection is continuous (see
* {@link MontageChannelsDataFlavor#isContinuous()})),</li>
* <li>{@link Montage#moveMontageChannelRange(int, int, int) moves} the
* specified channels to the specified position,</li>
* <li>updates the selection in the montage table.</li></ul></li></ul>
* <p>
* The element to which this transfer handler is assigned can also be the
* source of the transferable object. If the method {@link
* #createTransferable(JComponent)} is called the {@link MontageTransferable}
* is created with the channels selected in the table.
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class MontageTableTransferHandler extends TransferHandler {
/**
* the serialization constant
*/
private static final long serialVersionUID = 1L;
/**
* the logger
*/
protected static final Logger logger = Logger.getLogger(MontageTableTransferHandler.class);
/**
* the {@link SourceMontageChannelsDataFlavor flavor} for
* {@link SourceChannelIndices}
*/
private SourceMontageChannelsDataFlavor sourceFlavor = new SourceMontageChannelsDataFlavor();
/**
* the {@link MontageChannelsDataFlavor flavor} for
* {@link MontageChannelIndices}
*/
private MontageChannelsDataFlavor montageFlavor = new MontageChannelsDataFlavor(false);
/**
* @return {@link TransferHandler#MOVE}
*/
@Override
public int getSourceActions(JComponent c) {
return MOVE;
}
/**
* Checks which channels are selected and creates the array with their
* indexes. This array is used to create {@link MontageChannelIndices}
* and on the basis of it the {@link MontageTransferable}.
*/
@Override
protected Transferable createTransferable(JComponent c) {
MontageTable table = (MontageTable) c;
ListSelectionModel model = table.getSelectionModel();
int[] array;
int minIndex = model.getMinSelectionIndex();
if (minIndex < 0) {
array = new int[0];
} else {
int maxIndex = model.getMaxSelectionIndex();
if (maxIndex < 0) {
array = new int[0];
} else {
int cnt = 0;
int[] candidates = new int[maxIndex-minIndex+1];
for (int i=minIndex; i<=maxIndex; i++) {
if (model.isSelectedIndex(i)) {
candidates[cnt] = i;
cnt++;
}
}
array = Arrays.copyOf(candidates, cnt);
}
}
return new MontageTransferable(new MontageChannelIndices(array));
}
/**
* Returns <code>true</code> if at least one of the flavors in
* {@code transferFlavors} is either {@link SourceMontageChannelsDataFlavor}
* or {@link MontageChannelsDataFlavor} and <code>false</code> otherwise.
*/
@Override
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
logger.debug("Testing drop for [" + transferFlavors.length + "] flavors");
for (int i=0; i<transferFlavors.length; i++) {
logger.debug("Testing drop for flavor [" + transferFlavors[i].toString() + "]");
if (transferFlavors[i].equals(sourceFlavor)) {
logger.debug("Accepted source");
return true;
}
if (transferFlavors[i].equals(montageFlavor)) {
if (((MontageChannelsDataFlavor) transferFlavors[i]).isContinuous()) {
logger.debug("Accepted target");
return true;
}
}
}
logger.debug("Nothing interesting in this drop");
return false;
}
/**
* Performs the operation according to the type of the transferred object:
* <ul>
* <li>If transferred are {@link SourceChannelIndices}:
* <ul>
* <li>checks if the collection is of proper type and not empty,</li>
* <li>{@link Montage#addMontageChannels(int[], int) adds} the new
* {@link MontageChannel montage channels} (created on the basis of the
* {@link SourceChannel source channels} from the collection) to the
* {@link Montage montage} that is the model for the montage table.</li>
* </ul></li>
* <li>If transferred are {@link MontageChannelIndices}:
* <ul>
* <li>checks if the collection is of proper type and not empty,</li>
* <li>checks if the collection is continuous (see
* {@link MontageChannelsDataFlavor#isContinuous()})),</li>
* <li>{@link Montage#moveMontageChannelRange(int, int, int) moves} the
* specified channels to the specified position,</li>
* <li>updates the selection in the montage table.</li></ul></li></ul>
*/
@Override
public boolean importData(TransferSupport support) {
if (!support.isDrop()) {
return false;
}
MontageTable table = (MontageTable) support.getComponent();
DataFlavor[] dataFlavors = support.getDataFlavors();
if (dataFlavors == null || dataFlavors.length == 0) {
return false;
}
if (!canImport(table, dataFlavors)) {
return false;
}
JTable.DropLocation dropLocation = (JTable.DropLocation) support.getDropLocation();
if (dropLocation == null) {
return false;
}
Transferable transferable = support.getTransferable();
if (transferable.isDataFlavorSupported(sourceFlavor)) {
SourceChannelIndices indices = null;
try {
indices = (SourceChannelIndices) transferable.getTransferData(sourceFlavor);
} catch (UnsupportedFlavorException ex) {
logger.error("Failed to drop", ex);
return false;
} catch (IOException ex) {
logger.error("Failed to drop", ex);
return false;
}
if (indices == null) {
logger.warn("Drop empty, no indices");
return false;
}
int[] sourceChannels = indices.getSourceChannels();
if (sourceChannels == null || sourceChannels.length == 0) {
logger.warn("Drop empty, no rows in int[] table");
return false;
}
int row = dropLocation.getRow();
Montage montage = table.getModel().getMontage();
if (montage == null) {
logger.warn("No montage");
return false;
}
montage.addMontageChannels(sourceChannels, row);
} else if (transferable.isDataFlavorSupported(montageFlavor)) {
MontageChannelIndices indices = null;
try {
indices = (MontageChannelIndices) transferable.getTransferData(montageFlavor);
} catch (UnsupportedFlavorException ex) {
logger.error("Failed to drop", ex);
return false;
} catch (IOException ex) {
logger.error("Failed to drop", ex);
return false;
}
if (indices == null) {
logger.warn("Drop empty, no indices");
return false;
}
int[] montageChannels = indices.getMontageChannels();
if (montageChannels == null || montageChannels.length == 0) {
logger.warn("Drop empty, no rows in int[] table");
return false;
}
// check continuity
for (int i=0; i<(montageChannels.length-1); i++) {
if (montageChannels[i]+1 != montageChannels[i+1]) {
logger.debug("Not contiguous");
return false;
}
}
int row = dropLocation.getRow();
int lastMovedRow = montageChannels[0] + (montageChannels.length-1);
int delta = 0;
// analyze/correct the delta to make this more intuitive
if (row >= montageChannels[0] && row <= lastMovedRow) {
// the drop line is in the selection range, disregard
return false;
}
else if (row < montageChannels[0]) {
// the drop line is above, move normally
delta = row - montageChannels[0];
} else {
// the drop line is below - make sure that the end effect is that
// the dragged row end up between the rows between which the line was
delta = (row - montageChannels[0]) - montageChannels.length;
}
Montage montage = table.getModel().getMontage();
if (montage == null) {
logger.warn("No montage");
return false;
}
int movedDelta = montage.moveMontageChannelRange(montageChannels[0], montageChannels.length, delta);
table.getSelectionModel().setSelectionInterval(montageChannels[0]+movedDelta, lastMovedRow+movedDelta);
} else {
return false;
}
return true;
}
@Override
protected void exportDone(JComponent source, Transferable data, int action) {
}
}