001package jmri.jmrit.beantable.oblock;
002
003import java.awt.Point;
004import java.awt.datatransfer.DataFlavor;
005import java.awt.datatransfer.StringSelection;
006import java.awt.datatransfer.Transferable;
007import java.awt.datatransfer.UnsupportedFlavorException;
008import java.awt.dnd.DnDConstants;
009import java.awt.dnd.DragGestureEvent;
010import java.awt.dnd.DragGestureListener;
011import java.awt.dnd.DragSource;
012import java.awt.dnd.DragSourceDragEvent;
013import java.awt.dnd.DragSourceDropEvent;
014import java.awt.dnd.DragSourceEvent;
015import java.awt.dnd.DragSourceListener;
016import java.awt.dnd.DropTarget;
017import java.awt.dnd.DropTargetDragEvent;
018import java.awt.dnd.DropTargetDropEvent;
019import java.awt.dnd.DropTargetEvent;
020import java.awt.dnd.DropTargetListener;
021import java.io.IOException;
022import javax.annotation.Nonnull;
023import javax.swing.JComponent;
024import javax.swing.JInternalFrame;
025import javax.swing.JTable;
026import javax.swing.JTextField;
027import javax.swing.TransferHandler;
028import javax.swing.table.AbstractTableModel;
029import javax.swing.table.TableModel;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Support for GUI to define OBlocks and its parts.
035 * <hr>
036 * This file is part of JMRI.
037 * <p>
038 * JMRI is free software; you can redistribute it and/or modify it under the
039 * terms of version 2 of the GNU General Public License as published by the Free
040 * Software Foundation. See the "COPYING" file for a copy of this license.
041 * <p>
042 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
043 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
044 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
045 *
046 * @author Pete Cressman (C) 2010
047 */
048public class DnDJTable extends JTable implements DropTargetListener,
049        DragGestureListener, DragSourceListener, Transferable {
050
051    public static final String TableCellFlavorMime = DataFlavor.javaJVMLocalObjectMimeType
052            + ";class=jmri.jmrit.beantable.oblock.DnDJTable.TableCellSelection";
053    public static final DataFlavor TABLECELL_FLAVOR = new DataFlavor(
054            jmri.jmrit.beantable.oblock.DnDJTable.TableCellSelection.class,
055            "application/x-jmri.jmrit.beantable.oblock.DnDJTable.TableCellSelection");
056
057    private Point _dropPoint;
058    private int[] _skipCols;
059
060    DnDJTable(TableModel model, int[] skipCols) {
061        super(model);
062        this.setTransferHandler(new DnDHandler(this));
063        _skipCols = skipCols;
064        DragSource dragSource = DragSource.getDefaultDragSource();
065        dragSource.createDefaultDragGestureRecognizer(this,
066                DnDConstants.ACTION_COPY_OR_MOVE, this);
067        new DropTarget(this, DnDConstants.ACTION_COPY, this);
068    }
069
070    @Override
071    public boolean editCellAt(int row, int column, java.util.EventObject e) {
072        boolean res = super.editCellAt(row, column, e);
073        java.awt.Component c = this.getEditorComponent();
074        if (c instanceof javax.swing.JTextField) {
075            ((JTextField) c).selectAll();
076        }
077        return res;
078    }
079
080    Point getDropPoint() {
081        return _dropPoint;
082    }
083
084    private boolean dropOK(DropTargetDragEvent evt) {
085        Transferable tr = evt.getTransferable();
086        if (tr.isDataFlavorSupported(TABLECELL_FLAVOR)
087                || tr.isDataFlavorSupported(DataFlavor.stringFlavor)) {
088            _dropPoint = evt.getLocation();
089            //DnDHandler handler = (DnDHandler)getTransferHandler();
090            int col = columnAtPoint(_dropPoint);
091            int row = rowAtPoint(_dropPoint);
092            for (int skipCol : _skipCols) {
093                if (skipCol == col) {
094                    return false;
095                }
096            }
097            if (tr.isDataFlavorSupported(TABLECELL_FLAVOR)) {
098                try {
099                    // don't allow a cell import back into the cell exported from 
100                    TableCellSelection tcss = (TableCellSelection) tr.getTransferData(TABLECELL_FLAVOR);
101                    if (row == tcss.getRow() && col == tcss.getCol() && this == tcss.getTable()) {
102                        return false;
103                    }
104                } catch (UnsupportedFlavorException | IOException ex) {
105                    log.warn("DnDJTable.importData: at table {} e= ", getName(), ex);
106                    return false;
107                }
108            }
109        } else {
110            return false;
111        }
112        return true;
113    }
114
115    /**
116     * ************************* DropTargetListener ***********************
117     */
118    @Override
119    public void dragExit(DropTargetEvent evt) {
120        // log.debug("DnDJTable.dragExit ");
121        //evt.getDropTargetContext().acceptDrag(DnDConstants.ACTION_COPY);
122    }
123
124    @Override
125    public void dragEnter(DropTargetDragEvent evt) {
126        // log.debug("DnDJTable.dragEnter ");
127        if (!dropOK(evt)) {
128            evt.rejectDrag();
129        }
130    }
131
132    @Override
133    public void dragOver(DropTargetDragEvent evt) {
134        if (!dropOK(evt)) {
135            evt.rejectDrag();
136        }
137    }
138
139    @Override
140    public void dropActionChanged(DropTargetDragEvent dtde) {
141        // log.debug("DnDJTable.dropActionChanged ");
142    }
143
144    @Override
145    public void drop(DropTargetDropEvent evt) {
146        try {
147            Point pt = evt.getLocation();
148            String data;
149            Transferable tr = evt.getTransferable();
150            if (tr.isDataFlavorSupported(TABLECELL_FLAVOR)
151                    || tr.isDataFlavorSupported(DataFlavor.stringFlavor)) {
152                AbstractTableModel model = (AbstractTableModel) getModel();
153                int col = columnAtPoint(pt);
154                int row = rowAtPoint(pt);
155                if (col >= 0 && row >= 0) {
156                    row = convertRowIndexToModel(row);
157                    TableCellSelection sel = (TableCellSelection) tr.getTransferData(TABLECELL_FLAVOR);
158                    data = (String) sel.getTransferData(DataFlavor.stringFlavor);
159                    model.setValueAt(data, row, col);
160                    model.fireTableDataChanged();
161                    // log.debug("DnDJTable.drop: data= {} dropped at ({}, {})", data, row, col);
162                    evt.dropComplete(true);
163                    return;
164                }
165            } else {
166                log.warn("TransferHandler.importData: supported DataFlavors not avaialable at table from {}", tr.getClass().getName());
167            }
168        } catch (IOException ioe) {
169            log.warn("caught IOException", ioe);
170        } catch (UnsupportedFlavorException ufe) {
171            log.warn("caught UnsupportedFlavorException", ufe);
172        }
173        log.debug("DropJTree.drop REJECTED!");
174        evt.rejectDrop();
175    }
176
177    /**
178     * ************** DragGestureListener **************
179     */
180    @Override
181    public void dragGestureRecognized(DragGestureEvent e) {
182        // log.debug("DnDJTable.dragGestureRecognized ");
183        //Transferable t = getTransferable(this);
184        //e.startDrag(DragSource.DefaultCopyDrop, this, this); 
185    }
186
187    /**
188     * ************** DragSourceListener ***********
189     */
190    @Override
191    public void dragDropEnd(DragSourceDropEvent e) {
192        // log.debug("DnDJTable.dragDropEnd ");
193    }
194
195    @Override
196    public void dragEnter(DragSourceDragEvent e) {
197        // log.debug("DnDJTable.DragSourceDragEvent ");
198    }
199
200    @Override
201    public void dragExit(DragSourceEvent e) {
202        // log.debug("DnDJTable.dragExit ");
203    }
204
205    @Override
206    public void dragOver(DragSourceDragEvent e) {
207        // log.debug("DnDJTable.dragOver ");
208    }
209
210    @Override
211    public void dropActionChanged(DragSourceDragEvent e) {
212        // log.debug("DnDJTable.dropActionChanged ");
213    }
214
215    /**
216     * ************* Transferable ********************
217     */
218    @Override
219    public DataFlavor[] getTransferDataFlavors() {
220        // log.debug("DnDJTable.getTransferDataFlavors ");
221        return new DataFlavor[]{TABLECELL_FLAVOR};
222    }
223
224    @Override
225    public boolean isDataFlavorSupported(DataFlavor flavor) {
226        // log.debug("DnDJTable.isDataFlavorSupported ");
227        return TABLECELL_FLAVOR.equals(flavor);
228    }
229
230    @Nonnull
231    @Override
232    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
233        // log.debug("DnDJTable.getTransferData ");
234        if (isDataFlavorSupported(TABLECELL_FLAVOR)) {
235            int row = getSelectedRow();
236            int col = getSelectedColumn();
237            if (col >= 0 && row >= 0) {
238                row = convertRowIndexToModel(row);
239                return getValueAt(row, col);
240            }
241        }
242        return "";
243    }
244
245    static class TableCellSelection extends StringSelection {
246
247        int _row;
248        int _col;
249        JTable _table;
250
251        TableCellSelection(String data, int row, int col, JTable table) {
252            super(data);
253            _row = row;
254            _col = col;
255            _table = table;
256        }
257
258        int getRow() {
259            return _row;
260        }
261
262        int getCol() {
263            return _col;
264        }
265
266        JTable getTable() {
267            return _table;
268        }
269    }
270
271    static class TableCellTransferable implements Transferable {
272
273        TableCellSelection _tcss;
274
275        TableCellTransferable(TableCellSelection tcss) {
276            _tcss = tcss;
277        }
278
279        @Override
280        public DataFlavor[] getTransferDataFlavors() {
281            return new DataFlavor[]{TABLECELL_FLAVOR, DataFlavor.stringFlavor};
282        }
283
284        @Override
285        public boolean isDataFlavorSupported(DataFlavor flavor) {
286            return (flavor.equals(TABLECELL_FLAVOR) || flavor.equals(DataFlavor.stringFlavor));
287        }
288
289        @Nonnull
290        @Override
291        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
292            if (flavor.equals(TABLECELL_FLAVOR)) {
293                return _tcss;
294            } else if (flavor.equals(DataFlavor.stringFlavor)) {
295                return _tcss.getTransferData(DataFlavor.stringFlavor);
296            }
297            throw new UnsupportedFlavorException(flavor);
298        }
299    }
300
301    static class DnDHandler extends TransferHandler {
302
303        JTable _table;
304
305        DnDHandler(JTable table) {
306            _table = table;
307        }
308
309        //////////////export
310        @Override
311        public int getSourceActions(JComponent c) {
312            return COPY;
313        }
314
315        @Override
316        public Transferable createTransferable(JComponent c) {
317            if (c instanceof JTable) {
318                JTable table = (JTable) c;
319                int col = table.getSelectedColumn();
320                int row = table.getSelectedRow();
321                if (col < 0 || row < 0) {
322                    return null;
323                }
324                row = table.convertRowIndexToModel(row);
325                // log.debug("DnDHandler.createTransferable: at table "+
326                //                                    getName()+" from ("+row+", "+col+") data= \""
327                //                                    +table.getModel().getValueAt(row, col)+"\"");
328                TableCellSelection tcss = new TableCellSelection((String) table.getModel().getValueAt(row, col), row, col, _table);
329                return new TableCellTransferable(tcss);
330            }
331            return null;
332        }
333    
334        @Override
335        public void exportDone(JComponent c, Transferable t, int action) {
336            // log.debug("DnDHandler.exportDone at table ");
337        }
338
339        /////////////////////import
340        @Override
341        public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
342
343            boolean canDoIt = false;
344            for (DataFlavor transferFlavor : transferFlavors) {
345                if (transferFlavor.equals(TABLECELL_FLAVOR) || transferFlavor.equals(DataFlavor.stringFlavor)) {
346                    if (comp instanceof JTable) {
347                        canDoIt = true;
348                        break;
349                    }
350                }
351            }
352            return canDoIt;
353        }
354
355        @Override
356        public boolean importData(JComponent comp, Transferable tr) {
357            DataFlavor[] flavors = new DataFlavor[]{TABLECELL_FLAVOR, DataFlavor.stringFlavor};
358
359            if (!canImport(comp, flavors)) {
360                return false;
361            }
362
363            try {
364                if (tr.isDataFlavorSupported(TABLECELL_FLAVOR)
365                        || tr.isDataFlavorSupported(DataFlavor.stringFlavor)) {
366                    if (comp instanceof DnDJTable) {
367                        DnDJTable table = (DnDJTable) comp;
368                        AbstractTableModel model = (AbstractTableModel) table.getModel();
369                        int col = table.getSelectedColumn();
370                        int row = table.getSelectedRow();
371                        if (col >= 0 && row >= 0) {
372                            row = table.convertRowIndexToView(row);
373                            String data = (String) tr.getTransferData(DataFlavor.stringFlavor);
374                            model.setValueAt(data, row, col);
375                            model.fireTableDataChanged();
376                            java.awt.Container parent = table;
377                            do {
378                                parent = parent.getParent();
379                            } while (parent != null && !(parent instanceof JInternalFrame));
380                            if (parent != null) {
381                                ((JInternalFrame) parent).moveToFront();
382                            }
383                            log.debug("DnDHandler.importData: data= {} dropped at ({}, {})", data, row, col);
384                            return true;
385                        }
386                    }
387                }
388            } catch (UnsupportedFlavorException | IOException ex) {
389                log.warn("DnDHandler.importData: at table e= ", ex);
390            }
391            return false;
392        }
393    }
394
395    private final static Logger log = LoggerFactory.getLogger(DnDJTable.class);
396
397}