55eb753fb75a5769d28f746314eb2135b65600a3
[debian/jabref.git] / src / java / net / sf / jabref / groups / EntryTableTransferHandler.java
1 /*
2  All programs in this directory and subdirectories are published under the 
3  GNU General Public License as described below.
4
5  This program is free software; you can redistribute it and/or modify it 
6  under the terms of the GNU General Public License as published by the Free 
7  Software Foundation; either version 2 of the License, or (at your option) 
8  any later version.
9
10  This program is distributed in the hope that it will be useful, but WITHOUT 
11  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
12  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 
13  more details.
14
15  You should have received a copy of the GNU General Public License along 
16  with this program; if not, write to the Free Software Foundation, Inc., 59 
17  Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19  Further information about the GNU GPL is available at:
20  http://www.gnu.org/copyleft/gpl.ja.html
21  */
22
23 package net.sf.jabref.groups;
24
25 import java.awt.datatransfer.Clipboard;
26 import java.awt.datatransfer.DataFlavor;
27 import java.awt.datatransfer.Transferable;
28 import java.awt.datatransfer.UnsupportedFlavorException;
29 import java.awt.dnd.DnDConstants;
30 import java.awt.event.InputEvent;
31 import java.io.File;
32 import java.io.FileWriter;
33 import java.io.IOException;
34 import java.net.URL;
35 import java.util.ArrayList;
36 import java.util.Iterator;
37 import java.util.List;
38
39 import javax.swing.JComponent;
40 import javax.swing.JTable;
41 import javax.swing.TransferHandler;
42
43 import net.sf.jabref.BasePanel;
44 import net.sf.jabref.Globals;
45 import net.sf.jabref.JabRefFrame;
46 import net.sf.jabref.external.DroppedFileHandler;
47 import net.sf.jabref.external.ExternalFileType;
48 import net.sf.jabref.gui.MainTable;
49 import net.sf.jabref.imports.ImportMenuItem;
50 import net.sf.jabref.imports.OpenDatabaseAction;
51 import net.sf.jabref.imports.ParserResult;
52 import net.sf.jabref.net.URLDownload;
53
54 public class EntryTableTransferHandler extends TransferHandler {
55
56         protected final MainTable entryTable;
57
58         protected JabRefFrame frame;
59
60         private BasePanel panel;
61
62         protected DataFlavor urlFlavor;
63
64         protected DataFlavor stringFlavor;
65
66         protected static boolean DROP_ALLOWED = true;
67
68         /**
69          * Construct the transfer handler.
70          * 
71          * @param entryTable
72          *            The table this transfer handler should operate on. This
73          *            argument is allowed to equal
74          * @null, in which case the transfer handler can assume that it works for a
75          *        JabRef instance with no databases open, attached to the empty
76          *        tabbed pane.
77          * @param frame
78          *            The JabRefFrame instance.
79          * @param panel
80          *            The BasePanel this transferhandler works for.
81          */
82         public EntryTableTransferHandler(MainTable entryTable, JabRefFrame frame, BasePanel panel) {
83                 this.entryTable = entryTable;
84                 this.frame = frame;
85                 this.panel = panel;
86                 stringFlavor = DataFlavor.stringFlavor;
87                 try {
88                         urlFlavor = new DataFlavor("application/x-java-url; class=java.net.URL");
89                 } catch (ClassNotFoundException e) {
90                         Globals.logger("Unable to configure drag and drop for main table");
91                         e.printStackTrace();
92                 }
93         }
94
95         /**
96          * Overriden to indicate which types of drags are supported (only LINK).
97          * 
98          * @override
99          */
100         public int getSourceActions(JComponent c) {
101                 return DnDConstants.ACTION_LINK;
102         }
103
104         /**
105          * This method is called when dragging stuff *from* the table.
106          */
107         public Transferable createTransferable(JComponent c) {
108                 /* so we can assume it will never be called if entryTable==null: */
109                 return new TransferableEntrySelection(entryTable.getSelectedEntries());
110         }
111
112         /**
113          * This method is called when stuff is drag to the component.
114          * 
115          * Imports the dropped URL or plain text as a new entry in the current
116          * database.
117          * 
118          * @todo It would be nice to support dropping of pdfs onto the table as a
119          *       way to link them to the corresponding entries.
120          */
121         public boolean importData(JComponent comp, Transferable t) {
122
123                 // If the drop target is the main table, we want to record which
124                 // row the item was dropped on, to identify the entry if needed:
125                 int dropRow = -1;
126                 if (comp instanceof JTable) {
127                         dropRow = ((JTable) comp).getSelectedRow();
128                 }
129
130                 try {
131
132                         // This flavor is used for dragged file links in Windows:
133                         if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
134                                 // JOptionPane.showMessageDialog(null, "Received
135                                 // javaFileListFlavor");
136                                 List l = (List) t.getTransferData(DataFlavor.javaFileListFlavor);
137                                 return handleDraggedFiles(l, dropRow);
138                         }
139
140                         if (t.isDataFlavorSupported(urlFlavor)) {
141                                 URL dropLink = (URL) t.getTransferData(urlFlavor);
142                                 return handleDropTransfer(dropLink, dropRow);
143                         }
144
145                         if (t.isDataFlavorSupported(stringFlavor)) {
146                                 // JOptionPane.showMessageDialog(null, "Received stringFlavor:
147                                 // "+dropStr);
148                                 String dropStr = (String) t.getTransferData(stringFlavor);
149                                 return handleDropTransfer(dropStr, dropRow);
150                         }
151
152                 } catch (IOException ioe) {
153                         System.err.println("failed to read dropped data: " + ioe.toString());
154                 } catch (UnsupportedFlavorException ufe) {
155                         System.err.println("drop type error: " + ufe.toString());
156                 }
157
158                 // all supported flavors failed
159                 System.err.println("can't transfer input: ");
160                 DataFlavor inflavs[] = t.getTransferDataFlavors();
161                 for (int i = 0; i < inflavs.length; i++) {
162                         System.out.println("  " + inflavs[i].toString());
163                 }
164
165                 return false;
166         }
167
168         /**
169          * This method is called to query whether the transfer can be imported.
170          * 
171          * Will return true for urls, strings, javaFileLists
172          * 
173          * @override
174          */
175         public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
176                 if (!DROP_ALLOWED)
177                         return false;
178
179                 // accept this if any input flavor matches any of our supported flavors
180                 for (int i = 0; i < transferFlavors.length; i++) {
181                         DataFlavor inflav = transferFlavors[i];
182                         if (inflav.match(urlFlavor) || inflav.match(stringFlavor)
183                                 || inflav.match(DataFlavor.javaFileListFlavor))
184                                 return true;
185                 }
186
187                 // System.out.println("drop type forbidden");
188                 // nope, never heard of this type
189                 return false;
190         }
191
192         public void exportAsDrag(JComponent comp, InputEvent e, int action) {
193                 // action is always LINK
194                 super.exportAsDrag(comp, e, DnDConstants.ACTION_LINK);
195         }
196
197         protected void exportDone(JComponent source, Transferable data, int action) {
198                 // default implementation is OK
199                 super.exportDone(source, data, action);
200         }
201
202         public void exportToClipboard(JComponent comp, Clipboard clip, int action) {
203                 // default implementation is OK
204                 super.exportToClipboard(comp, clip, action);
205         }
206
207         // add-ons -----------------------
208
209         protected boolean handleDropTransfer(String dropStr, final int dropRow) throws IOException {
210                 if (dropStr.startsWith("file:")) {
211                         // This appears to be a dragged file link and not a reference
212                         // format. Check if we can map this to a set of files:
213                         if (handleDraggedFilenames(dropStr, dropRow))
214                                 return true;
215                         // If not, handle it in the normal way...
216                 } else if (dropStr.startsWith("http:")) {
217                         // This is the way URL links are received on OS X and KDE (Gnome?):
218                         URL url = new URL(dropStr);
219                         // JOptionPane.showMessageDialog(null, "Making URL:
220                         // "+url.toString());
221                         return handleDropTransfer(url, dropRow);
222                 }
223                 File tmpfile = java.io.File.createTempFile("jabrefimport", "");
224                 tmpfile.deleteOnExit();
225                 FileWriter fw = new FileWriter(tmpfile);
226                 fw.write(dropStr);
227                 fw.close();
228
229                 // System.out.println("importing from " + tmpfile.getAbsolutePath());
230
231                 ImportMenuItem importer = new ImportMenuItem(frame, false);
232                 importer.automatedImport(new String[] { tmpfile.getAbsolutePath() });
233
234                 return true;
235         }
236
237         /**
238          * Handle a String describing a set of files or URLs dragged into JabRef.
239          * 
240          * @param s
241          *            String describing a set of files or URLs dragged into JabRef
242      * @param dropRow The row in the table where the files were dragged.
243      * @return success status for the operation
244      *
245          */
246         private boolean handleDraggedFilenames(String s, final int dropRow) {
247                 // Split into lines:
248                 String[] lines = s.replaceAll("\r", "").split("\n");
249                 List files = new ArrayList();
250                 for (int i = 0; i < lines.length; i++) {
251                         String line = lines[i];
252                         if (line.startsWith("file:"))
253                                 line = line.substring(5);
254                         else
255                                 continue;
256                         // Under Gnome, the link is given as file:///...., so we
257                         // need to strip the extra slashes:
258                         if (line.startsWith("//"))
259                                 line = line.substring(2);
260                         File f = new File(line);
261                         if (f.exists()) {
262                                 files.add(f);
263                         }
264                 }
265                 return handleDraggedFiles(files, dropRow);
266
267         }
268
269         /**
270          * Handle a List containing File objects for a set of files to import.
271          * 
272          * @param files
273          *            A List containing File instances pointing to files.
274          * @param dropRow @param dropRow The row in the table where the files were dragged.
275      * @return success status for the operation
276          */
277         private boolean handleDraggedFiles(List files, final int dropRow) {
278                 final String[] fileNames = new String[files.size()];
279                 int i = 0;
280                 for (Iterator iterator = files.iterator(); iterator.hasNext();) {
281                         File file = (File) iterator.next();
282                         fileNames[i] = file.getAbsolutePath();
283                         i++;
284                 }
285                 // Try to load bib files normally, and import the rest into the current
286                 // database.
287                 // This process must be spun off into a background thread:
288                 new Thread(new Runnable() {
289                         public void run() {
290                                 loadOrImportFiles(fileNames, dropRow);
291                         }
292                 }).start();
293
294                 return true;
295         }
296
297         /**
298          * Take a set of filenames. Those with names indicating bib files are opened
299          * as such if possible. All other files we will attempt to import into the
300          * current database.
301          * 
302          * @param fileNames
303          *            The names of the files to open.
304          * @param dropRow success status for the operation
305          */
306         private void loadOrImportFiles(String[] fileNames, int dropRow) {
307
308                 OpenDatabaseAction openAction = new OpenDatabaseAction(frame, false);
309                 ArrayList notBibFiles = new ArrayList();
310                 String encoding = Globals.prefs.get("defaultEncoding");
311                 for (int i = 0; i < fileNames.length; i++) {
312                         // Find the file's extension, if any:
313                         String extension = "";
314                         ExternalFileType fileType = null;
315                         int index = fileNames[i].lastIndexOf('.');
316                         if ((index >= 0) && (index < fileNames[i].length())) {
317                                 extension = fileNames[i].substring(index + 1).toLowerCase();
318                                 // System.out.println(extension);
319                                 fileType = Globals.prefs.getExternalFileTypeByExt(extension);
320                         }
321                         if (extension.equals("bib")) {
322                                 File f = new File(fileNames[i]);
323                                 try {
324                                         ParserResult pr = OpenDatabaseAction.loadDatabase(f, encoding);
325                                         if ((pr == null) || (pr == ParserResult.INVALID_FORMAT)) {
326                                                 notBibFiles.add(fileNames[i]);
327                                         } else {
328                                                 openAction.addNewDatabase(pr, f, true);
329                         frame.getFileHistory().newFile(fileNames[i]);
330                     }
331                                 } catch (IOException e) {
332                                         notBibFiles.add(fileNames[i]);
333                                         // No error message, since we want to try importing the
334                                         // file?
335                                         //
336                                         // Util.showQuickErrorDialog(frame, Globals.lang("Open
337                                         // database"), e);
338                                 }
339                                 continue;
340                         }
341
342                         /*
343                          * This is a linkable file. If the user dropped it on an entry, we
344                          * should offer options for autolinking to this files:
345                          * 
346                          * TODO we should offer an option to highlight the row the user is on too.
347                          */
348                         if (fileType != null && dropRow >= 0) {
349
350                                 /*
351                                  * TODO: need to signal if this is a local or autodownloaded
352                                  * file
353                                  */
354                                 boolean local = true;
355
356                                 /*
357                                  * TODO: make this an instance variable?
358                                  */
359                                 DroppedFileHandler dfh = new DroppedFileHandler(frame, panel);
360                                 dfh.handleDroppedfile(fileNames[i], fileType, local, entryTable, dropRow);
361
362                                 continue;
363                         }
364 /*
365                         if (extension.equals("pdf")) {
366                                 Collection c;
367                                 try {
368                                         c = XMPUtil.readXMP(fileNames[i]);
369                                 } catch (IOException e1) {
370                                         c = null;
371                                         frame.output(Globals.lang("No XMP metadata found in " + fileNames[i]));
372                                 }
373
374                                 if (c != null && c.size() > 0) {
375                                         Iterator it = c.iterator();
376
377                                         BasePanel panel = frame.basePanel();
378
379                                         if (panel == null) {
380                                                 // // Create a new, empty, database.
381                                                 BibtexDatabase database = new BibtexDatabase();
382                                                 frame.addTab(database, null, null, Globals.prefs.get("defaultEncoding"),
383                                                         true);
384                                                 frame.output(Globals.lang("New database created."));
385                                                 panel = frame.basePanel();
386                                         }
387
388                                         BibtexDatabase database = frame.basePanel().database();
389
390                                         NamedCompound ce = new NamedCompound(Globals.lang("Drog PDF"));
391
392                                         while (it.hasNext()) {
393                                                 BibtexEntry e = (BibtexEntry) it.next();
394
395                                                 try {
396                                                         e.setId(Util.createNeutralId());
397                                                         database.insertEntry(e);
398                                                         ce.addEdit(new UndoableInsertEntry(database, e, panel));
399                                                 } catch (Exception e2) {
400                                                         // Should not happen?
401                                                 }
402                                         }
403
404                                         ce.end();
405                                         panel.undoManager.addEdit(ce);
406                                         panel.markBaseChanged();
407                                         continue;
408                                 }
409                         }
410                         */
411
412                         notBibFiles.add(fileNames[i]);
413                 }
414
415                 if (notBibFiles.size() > 0) {
416                         String[] toImport = new String[notBibFiles.size()];
417                         notBibFiles.toArray(toImport);
418
419                         // Import into new if entryTable==null, otherwise into current
420                         // database:
421                         ImportMenuItem importer = new ImportMenuItem(frame, (entryTable == null));
422                         importer.automatedImport(toImport);
423                 }
424         }
425
426         protected boolean handleDropTransfer(URL dropLink, int dropRow) throws IOException {
427                 File tmpfile = java.io.File.createTempFile("jabrefimport", "");
428                 tmpfile.deleteOnExit();
429
430                 // System.out.println("Import url: " + dropLink.toString());
431                 // System.out.println("Temp file: "+tmpfile.getAbsolutePath());
432
433                 new URLDownload(entryTable, dropLink, tmpfile).download();
434
435                 // Import into new if entryTable==null, otherwise into current database:
436                 ImportMenuItem importer = new ImportMenuItem(frame, (entryTable == null));
437                 importer.automatedImport(new String[] { tmpfile.getAbsolutePath() });
438
439                 return true;
440         }
441
442 }