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