001    /**
002     * iFish - An iRiver iHP jukebox database creation tool
003     *
004     * Copyright (C) 2009 Richard "Shred" Körber
005     *   http://ifish.shredzone.org
006     *
007     * This program is free software: you can redistribute it and/or modify
008     * it under the terms of the GNU General Public License as published by
009     * the Free Software Foundation, either version 3 of the License, or
010     * (at your option) any later version.
011     *
012     * This program is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015     * GNU General Public License for more details.
016     *
017     * You should have received a copy of the GNU General Public License
018     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
019     */
020    package net.shredzone.ifish.gui;
021    
022    import java.awt.datatransfer.DataFlavor;
023    import java.awt.datatransfer.Transferable;
024    import java.awt.datatransfer.UnsupportedFlavorException;
025    import java.io.File;
026    import java.io.IOException;
027    import java.util.ArrayList;
028    import java.util.Iterator;
029    
030    import javax.swing.JComponent;
031    import javax.swing.JTable;
032    import javax.swing.TransferHandler;
033    
034    import net.shredzone.ifish.IFishPane;
035    import net.shredzone.ifish.db.Entry;
036    import net.shredzone.ifish.db.Playlist;
037    
038    /**
039     * A transfer handler that allows to order Entry objects by drag and drop.
040     *
041     * @author    Richard Körber &lt;dev@shredzone.de&gt;
042     * @version   $Id: EntryTransferHandler.java 291 2009-04-28 21:29:27Z shred $
043     */
044    public class EntryTransferHandler extends TransferHandler {
045      private static final long serialVersionUID = 4121130320965023281L;
046      private final IFishPane fish;
047    
048      /**
049       * Create a new EntryTransferHandler
050       * 
051       * @param fish    Reference to IFishPane
052       */
053      public EntryTransferHandler( IFishPane fish ) {
054        this.fish = fish;
055      }
056      
057      /**
058       * Get allowed source actions. This TransferHandler only supports MOVE since
059       * we want to sort JTable entries.
060       * 
061       * @param   c         JComponent that initiates the DnD action.
062       * @return  Source action, MOVE here.
063       */
064      @Override
065      public int getSourceActions( JComponent c ) {
066        return MOVE;
067      }
068      
069      /**
070       * Check if this TransferHandler accepts one of the given flavors. Only
071       * <code>EntryContainer.entryFlavor</code> is accepted here.
072       * 
073       * @param   comp      JComponent that is the target of the DnD action.
074       * @param   transferFlavors     DataFlavors to be checked
075       * @return  true if this TransferHandler accepts one of the flavors.
076       */
077      @Override
078      public boolean canImport( JComponent comp, DataFlavor[] transferFlavors ) {
079        for( int ix=0; ix<transferFlavors.length; ix++ ) {
080          if( EntryContainer.entryFlavor.equals( transferFlavors[ix] ) )
081            return true;
082        }
083        return false;
084      }
085    
086      /**
087       * Create a Transferable. The Transferable will contain all Entry
088       * objects that have been selected.
089       * 
090       * @param   comp      JComponent that is the source of the DnD action.
091       * @return  Transferable containing the data.
092       */
093      @Override
094      public Transferable createTransferable( JComponent comp ) {
095        EntryContainer container = new EntryContainer();
096        
097        //--- Fetch all selected Entry ---
098        JTable table = (JTable) comp;
099        PlaylistTableModel model = (PlaylistTableModel) table.getModel();
100        int[] rows = table.getSelectedRows();
101        for( int ix=0; ix<rows.length; ix++ ) {
102          container.add( model.getEntryAt( rows[ix] ) );
103        }
104        
105        //--- Create a Transferable ---
106        return new EntryTransferable( container );
107      }
108    
109      /**
110       * Import data after dropping them to the component.
111       * 
112       * @param   comp      JComponent that is the target of the DnD action.
113       * @param   t         Transferable containing the transported data.
114       * @return  Always true.
115       */
116      @Override
117      public boolean importData( JComponent comp, Transferable t ) {
118        try {
119          EntryContainer container = (EntryContainer) t.getTransferData( EntryContainer.entryFlavor );
120    
121          //--- Get the table and the current position ---
122          JTable table = (JTable) comp;
123          PlaylistTableModel model = (PlaylistTableModel) table.getModel();
124          Playlist pl = model.getPlaylist();
125          int pos = table.getSelectedRow();
126          pos = Math.max( pos, 0 );
127          
128          //--- All all Entry to the table ---
129          for (Entry entry : container) {
130            if( pl.contains(entry) ) {            // Only accept existing entries
131              pos = pl.insertEntry( pos, entry );
132            }
133          }
134          fish.setUnsaved( true );
135          
136          //--- Select the original target row ---
137          // It might have been shifted down in the meantime, so we have
138          // to select it again.
139          table.setRowSelectionInterval( pos,pos );
140        }catch( Exception e ) {}    // do nothing if an exception occured
141        return true;
142      }
143    
144    /*----------------------------------------------------------------------------*/
145      
146      /**
147       * An EntryContainer holds a selected list of Entry objects.
148       */
149      public static class EntryContainer extends ArrayList<Entry> {
150        private static final long serialVersionUID = 3258408422045923383L;
151    
152        /**
153         * A DataFlavor used for transporting EntryContainer via clipboard,
154         * drag&drop etc.
155         */
156        public final static DataFlavor entryFlavor =
157          new DataFlavor(
158              EntryContainer.class,
159              "application/x-java-serialized-object"
160          );
161    
162        /**
163         * Get a string representation. This will be the file name of all Entry
164         * files on the target machine, with one name in each row.
165         *  
166         * @return  String representation
167         */
168        @Override
169        public String toString() {
170          StringBuffer buff = new StringBuffer();
171          String linesepp = System.getProperty("line.separator");
172          
173          for (Entry e : this) {
174            buff.append( e.getFile().toString() );
175            buff.append( linesepp );
176          }
177          
178          return buff.toString();
179        }
180        
181      }
182    
183    /*----------------------------------------------------------------------------*/
184    
185      /**
186       * This is a Transferable that allows to transfer several Entry objects
187       * as Entry itself, as well as a list of Files and a plain string.
188       */
189      public static class EntryTransferable extends java.awt.datatransfer.StringSelection {
190        private final EntryContainer container;     // Container for the Entry objects
191        
192        /**
193         * Create a new EntryTransferable.
194         * 
195         * @param   entries       EntryContainer holding the Entry objects
196         */
197        public EntryTransferable( EntryContainer entries ) {
198          super( entries.toString() );
199          container = entries;
200        }
201        
202        /**
203         * Get all available DataFlavors. This is the <code>EntryContainer.entryFlavor</code>
204         * in first place, then <code>DataFlavor.javaFileListFlavor</code>,
205         * then all DataFlavor given by StringSelection.
206         * 
207         * @return    Array of all available DataFlavor.
208         */
209        @Override
210        public DataFlavor[] getTransferDataFlavors() {
211          //--- Get the flavors from the superclass ---
212          DataFlavor[] superflavor = super.getTransferDataFlavors();
213          
214          //--- Add our flavor to the front ---
215          DataFlavor[] result = new DataFlavor[superflavor.length+2];
216          System.arraycopy( superflavor,0, result,2, superflavor.length );
217          result[0] = EntryContainer.entryFlavor;
218          result[1] = DataFlavor.javaFileListFlavor;
219          
220          //--- Return the new flavor array ---
221          return result;
222        }
223        
224        /**
225         * Check if this Transferable supports a certain DataFlavor.
226         * 
227         * @param   flavor    DataFlavor to be checked
228         * @return  true if the DataFlavor is supported.
229         */
230        @Override
231        public boolean isDataFlavorSupported( DataFlavor flavor ) {
232          if( EntryContainer.entryFlavor.equals(flavor) ) return true;
233          if( DataFlavor.javaFileListFlavor.equals(flavor) ) return true;
234          
235          return super.isDataFlavorSupported( flavor );
236        }
237        
238        /**
239         * Get the transported data for a certain DataFlavor. For
240         * <code>EntryContainer.entryFlavor</code>, an EntryContainer object
241         * will be returned.
242         * 
243         * @param   flavor    DataFlavor to get the data for
244         * @return  Data object for this flavor.
245         * @throws  UnsupportedFlavorException  if the method was unable to get
246         *    a data object for the requested flavor.
247         */
248        @Override
249        public Object getTransferData( DataFlavor flavor )
250        throws UnsupportedFlavorException, IOException {
251          //--- EntryFlavor ---
252          // Just return the EntryContainer.
253          if( EntryContainer.entryFlavor.equals(flavor) ) {
254            return container;
255          }
256          
257          //--- FileListFlavor ---
258          // Return a list with a File object for each Entry.
259          if( DataFlavor.javaFileListFlavor.equals(flavor) ) {
260            ArrayList<File> lResult = new ArrayList<File>( container.size() );
261            for (Entry e : container) {
262              lResult.add( e.getFile() );
263            }
264            return lResult;
265          }
266          
267          //--- Otherwise return the super data ---
268          return super.getTransferData( flavor );
269        }
270      }
271    
272    }