001package jmri.jmrit.roster.swing.functiontable;
002
003import java.awt.datatransfer.*;
004import java.io.IOException;
005
006import javax.swing.*;
007
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
012import jmri.jmrit.roster.RosterEntry;
013import jmri.jmrit.roster.RosterFunctionEntry;
014import jmri.util.datatransfer.RosterEntrySelection;
015import jmri.util.iharder.dnd.URIDrop;
016import jmri.util.swing.EditableResizableImagePanel;
017
018/**
019 * A TransferHandler that handl drag'n drop over the FunctionTable. 
020 * It can handle rows drag'n drop but also drag'n drop into the EditableResizableImagePanel in some of the table cells.
021 * 
022 * It is used in the FunctionLabelPane.
023 * 
024 * <hr>
025 * This file is part of JMRI.
026 * <p>
027 * JMRI is free software; you can redistribute it and/or modify it under the
028 * terms of version 2 of the GNU General Public License as published by the Free
029 * Software Foundation. See the "COPYING" file for a copy of this license.
030 * <p>
031 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
032 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
033 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
034 *
035 * @author Lionel Jeanson 2009-2026
036 */
037
038public class FunctionTableRowTransferHandler extends TransferHandler {
039    private final JTable table;
040    private final DataFlavor rowFlavor = new DataFlavor(MyTransferData.class, "function table row+rosterentry+rosterfunctionentry");
041
042    public FunctionTableRowTransferHandler(JTable table) {
043        this.table = table;
044    }
045
046    @Override
047    protected Transferable createTransferable(JComponent c) {
048        return new DataHandler(table.getSelectedRow(), ((FunctionTableModel)table.getModel()).getRosterEntry(), ((FunctionTableModel)table.getModel()).getRosterFunctionEntryAtRow(table.getSelectedRow()), rowFlavor);
049    }
050
051    @Override
052    public boolean canImport(TransferSupport support) {
053        EditableResizableImagePanel impan = FunctionTableMouseListener.getEditableResizableImagePanelAt(table, support.getDropLocation().getDropPoint().x, support.getDropLocation().getDropPoint().y);
054        if (impan != null) {
055            for (DataFlavor df :  support.getDataFlavors()) {
056                if (df.isRepresentationClassReader()) {
057                    return true;
058                }
059            }
060            return ( support.isDataFlavorSupported(DataFlavor.imageFlavor) ||
061                     support.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ||
062                     support.isDataFlavorSupported(DataFlavor.stringFlavor));
063        }
064        return support.isDrop() && ( support.isDataFlavorSupported(rowFlavor) || support.isDataFlavorSupported(RosterEntrySelection.rosterEntryFlavor));
065    }
066
067    @Override
068    public int getSourceActions(JComponent c) {
069        return MOVE;
070    }
071
072    @Override
073    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "There is an instanceof test just before the cast.")
074    public boolean importData(TransferSupport support) {
075        if ( !canImport(support) || !(support.getDropLocation() instanceof JTable.DropLocation)) {
076            log.debug("TransferSupport() can't do import");
077            return false;
078        }
079        JTable.DropLocation drop = (JTable.DropLocation) support.getDropLocation();
080        // is if a drop on an imageicon?
081        EditableResizableImagePanel impan = FunctionTableMouseListener.getEditableResizableImagePanelAt(table, drop.getDropPoint().x, drop.getDropPoint().y);
082        if (impan != null) {
083            if ( URIDrop.importExternalTransferable(support.getTransferable(), impan)) {
084                return true;
085            }
086            log.debug("Drop on image, but not importable as image, trying other possibilities");
087        }
088        // a RosterEntry?
089        if (support.isDataFlavorSupported(RosterEntrySelection.rosterEntryFlavor)) {
090            log.debug("Dropping a roster entry -> copy all functions");
091            try {
092                RosterEntry re = RosterEntrySelection.getRosterEntries(support.getTransferable()).get(0);
093                ((FunctionTableModel)table.getModel()).reset(re.getFunctionEntries(), re.getShuntingFunction(), re.getAttribute(FunctionTableModel.FB_ORDER_ATTR));
094                return true;
095            } catch (UnsupportedFlavorException | IOException | ClassCastException e) {
096                log.error("Could not drag'n drop roster entry.", e);
097            }
098        }
099        // else must be a row
100        try {
101            MyTransferData data = (MyTransferData) support.getTransferable().getTransferData(rowFlavor);
102            int to = drop.getRow();
103            if ( data.getRosterEntry().getDccLocoAddress().equals( ((FunctionTableModel)table.getModel()).getRosterEntry().getDccLocoAddress() )) { 
104                log.debug("Dropping a row from the same roster entry -> reorder");
105                int from = data.getRow();
106                if (to < 0 || from == to || from + 1 == to) {
107                    log.debug("TransferSupport() can't do import : same indexes");
108                    return false;
109                }
110                ((FunctionTableModel) table.getModel()).moveRow(from, to > from ? to - 1 : to);
111                table.getSelectionModel().setSelectionInterval( to > from ? to - 1 : to, to > from ? to - 1 : to );
112            } else {
113                log.debug("Dropping a row from another roster entry -> copy function properties");
114                ((FunctionTableModel)table.getModel()).setRosterFunctionEntryAtRow(data.getRosterFunctionEntry(), to);
115            }
116            return true;
117        } catch (UnsupportedFlavorException | IOException e) {
118            log.debug("importData() Exception : ", e);
119        }
120        log.debug("Couldn't import.");
121        return false;
122    }
123
124    private static class DataHandler implements Transferable {
125        private final MyTransferData transferData;
126        private final DataFlavor flavor;
127
128        DataHandler(Integer row, RosterEntry re, RosterFunctionEntry fe, DataFlavor flavor) {
129            this.transferData = new MyTransferData(row, re, fe);
130            this.flavor = flavor;
131        }
132
133        @Override
134        public DataFlavor[] getTransferDataFlavors() {
135            return new DataFlavor[] { flavor };
136        }
137
138        @Override
139        public boolean isDataFlavorSupported(DataFlavor f) {
140            return flavor.equals(f);
141        }
142
143        @Override
144        public Object getTransferData(DataFlavor f)
145                throws UnsupportedFlavorException {
146            if (!isDataFlavorSupported(f)) {
147                throw new UnsupportedFlavorException(f);
148            }
149            return transferData;
150        }
151    }
152
153    private static class MyTransferData {
154        private final int row;
155        private final RosterEntry rosterEntry;
156        private final RosterFunctionEntry functionEntry;
157        
158        MyTransferData(int r, RosterEntry re, RosterFunctionEntry fe) {
159            row = r;
160            rosterEntry = re;
161            functionEntry = fe;
162        }
163
164        int getRow() {
165            return row;
166        }
167
168        RosterEntry getRosterEntry() {
169            return rosterEntry;
170        }
171
172        RosterFunctionEntry getRosterFunctionEntry() {
173            return functionEntry;
174        }        
175    }
176
177    private static final Logger log = LoggerFactory.getLogger(FunctionTableRowTransferHandler.class);
178}