e470337087809be620d2c90f2956d9f3a6f731d7
[debian/jabref.git] / src / java / net / sf / jabref / external / DroppedFileHandler.java
1 package net.sf.jabref.external;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.Iterator;
6 import java.util.List;
7
8 import javax.swing.ButtonGroup;
9 import javax.swing.JCheckBox;
10 import javax.swing.JLabel;
11 import javax.swing.JOptionPane;
12 import javax.swing.JPanel;
13 import javax.swing.JRadioButton;
14 import javax.swing.event.ChangeEvent;
15 import javax.swing.event.ChangeListener;
16
17 import net.sf.jabref.*;
18 import net.sf.jabref.gui.MainTable;
19 import net.sf.jabref.gui.FileListTableModel;
20 import net.sf.jabref.gui.FileListEntry;
21 import net.sf.jabref.undo.NamedCompound;
22 import net.sf.jabref.undo.UndoableFieldChange;
23 import net.sf.jabref.undo.UndoableInsertEntry;
24 import net.sf.jabref.util.XMPUtil;
25
26 import com.jgoodies.forms.builder.DefaultFormBuilder;
27 import com.jgoodies.forms.layout.FormLayout;
28
29 /**
30  * This class holds the functionality of autolinking to a file that's dropped
31  * onto an entry.
32  * 
33  * Options for handling the files are:
34  * 
35  * 1) Link to the file in its current position (disabled if the file is remote)
36  * 
37  * 2) Copy the file to ??? directory, rename after bibtex key, and extension
38  * 
39  * 3) Move the file to ??? directory, rename after bibtex key, and extension
40  */
41 public class DroppedFileHandler {
42     private JabRefFrame frame;
43
44     private BasePanel panel;
45
46     private JRadioButton linkInPlace = new JRadioButton(), copyRadioButton = new JRadioButton(),
47         moveRadioButton = new JRadioButton();
48     
49     private JLabel destDirLabel = new JLabel();
50
51     private JCheckBox renameCheckBox = new JCheckBox();
52
53     private JPanel optionsPanel = new JPanel();
54
55     public DroppedFileHandler(JabRefFrame frame, BasePanel panel) {
56
57         this.frame = frame;
58         this.panel = panel;
59
60         ButtonGroup grp = new ButtonGroup();
61         grp.add(linkInPlace);
62         grp.add(copyRadioButton);
63         grp.add(moveRadioButton);
64         copyRadioButton.setSelected(true);
65
66         DefaultFormBuilder builder = new DefaultFormBuilder(optionsPanel, new FormLayout(
67             "left:pref", ""));
68         builder.append(linkInPlace);
69         builder.append(destDirLabel);
70         builder.append(copyRadioButton);
71         builder.append(moveRadioButton);
72         builder.append(renameCheckBox);
73     }
74
75     /**
76      * Offer copy/move/linking options for a dragged external file. Perform the
77      * chosen operation, if any.
78      * 
79      * @param fileName
80      *            The name of the dragged file.
81      * @param fileType
82      *            The FileType associated with the file.
83      * @param localFile
84      *            Indicate whether this is a local file, or a remote file copied
85      *            to a local temporary file.
86      * @param mainTable
87      *            The MainTable the file was dragged to.
88      * @param dropRow
89      *            The row where the file was dropped.
90      */
91     public void handleDroppedfile(String fileName, ExternalFileType fileType, boolean localFile,
92         MainTable mainTable, int dropRow) {
93
94         BibtexEntry entry = mainTable.getEntryAt(dropRow);
95         handleDroppedfile(fileName, fileType, localFile, entry);
96     }
97
98     /**
99      *
100      * @param fileName
101      *        The name of the dragged file.
102      * @param fileType
103      *        The FileType associated with the file.
104      * @param localFile
105  *            Indicate whether this is a local file, or a remote file copied
106  *            to a local temporary file.
107      * @param entry
108      *        The target entry for the drop.
109      */
110     public void handleDroppedfile(String fileName, ExternalFileType fileType, boolean localFile,
111         BibtexEntry entry) {
112         NamedCompound edits = new NamedCompound(Globals.lang("Drop %0", fileType.extension));
113
114         if (tryXmpImport(fileName, fileType, localFile, edits)) {
115             edits.end();
116             panel.undoManager.addEdit(edits);
117             return;
118         }
119
120         // Show dialog
121         boolean newEntry = false;
122         boolean rename = entry.getCiteKey() != null && entry.getCiteKey().length() > 0;
123         String citeKeyOrReason = (rename ? entry.getCiteKey() : Globals.lang("Entry has no citekey"));
124         int reply = showLinkMoveCopyRenameDialog(Globals.lang("Link to file %0", fileName),
125             fileType, rename, citeKeyOrReason, newEntry, false);
126
127         if (reply != JOptionPane.OK_OPTION)
128             return;
129
130         /*
131          * Ok, we're ready to go. See first if we need to do a file copy before
132          * linking:
133          */
134         boolean success = true;
135         String destFilename;
136
137         if (linkInPlace.isSelected()) {
138             destFilename = fileName;
139         } else {
140             destFilename = (renameCheckBox.isSelected() ? entry.getCiteKey() + "." + fileType.extension
141                     : new File(fileName).getName());
142             if (copyRadioButton.isSelected()) {
143                 success = doCopy(fileName, fileType, destFilename, edits);
144             } else if (moveRadioButton.isSelected()) {
145                 success = doRename(fileName, fileType, destFilename, edits);
146             }
147         }
148
149         if (success) {
150             doLink(entry, fileType, destFilename, false, edits);
151             panel.markBaseChanged();
152         }
153         edits.end();
154         panel.undoManager.addEdit(edits);
155
156     }
157
158     private boolean tryXmpImport(String fileName, ExternalFileType fileType, boolean localFile,
159         NamedCompound edits) {
160
161         if (!fileType.extension.equals("pdf")) {
162             return false;
163         }
164
165         List xmpEntriesInFile = null;
166         try {
167             xmpEntriesInFile = XMPUtil.readXMP(fileName);
168         } catch (Exception e) {
169             return false;
170         }
171
172         if ((xmpEntriesInFile == null) || (xmpEntriesInFile.size() == 0)) {
173             return false;
174         }
175
176         JLabel confirmationMessage = new JLabel(
177             Globals
178                 .lang("The PDF contains one or several bibtex-records.\nDo you want to import these as new entries into the current database?"));
179
180         int reply = JOptionPane.showConfirmDialog(frame, confirmationMessage, Globals.lang(
181             "XMP metadata found in PDF: %0", fileName), JOptionPane.YES_NO_CANCEL_OPTION,
182             JOptionPane.QUESTION_MESSAGE);
183
184         if (reply == JOptionPane.CANCEL_OPTION) {
185             return true; // The user canceled thus that we are done.
186         }
187         if (reply == JOptionPane.NO_OPTION) {
188             return false;
189         }
190
191         // reply == JOptionPane.YES_OPTION)
192
193         /*
194          * TODO Extract Import functionality from ImportMenuItem then we could
195          * do:
196          * 
197          * ImportMenuItem importer = new ImportMenuItem(frame, (mainTable ==
198          * null), new PdfXmpImporter());
199          * 
200          * importer.automatedImport(new String[] { fileName });
201          */
202
203         boolean isSingle = xmpEntriesInFile.size() == 1;
204         BibtexEntry single = (isSingle ? (BibtexEntry) xmpEntriesInFile.get(0) : null);
205
206         /*reply = showLinkMoveCopyRenameDialog(Globals.lang("Link to PDF %0", fileName), fileType,
207             isSingle, (isSingle ? single.getCiteKey() : Globals.lang("Cannot rename for several entries.")),
208             false, !isSingle);*/
209
210         boolean success = true;
211
212         String destFilename;
213
214         if (linkInPlace.isSelected()) {
215             destFilename = fileName;
216         } else {
217             if (renameCheckBox.isSelected()) {
218                 destFilename = fileName;
219             } else {
220                 destFilename = single.getCiteKey() + "." + fileType.extension;
221             }
222
223             if (copyRadioButton.isSelected()) {
224                 success = doCopy(fileName, fileType, destFilename, edits);
225             } else if (moveRadioButton.isSelected()) {
226                 success = doRename(fileName, fileType, destFilename, edits);
227             }
228         }
229         if (success) {
230
231             Iterator it = xmpEntriesInFile.iterator();
232
233             while (it.hasNext()) {
234                 try {
235                     BibtexEntry entry = (BibtexEntry) it.next();
236                     entry.setId(Util.createNeutralId());
237                     edits.addEdit(new UndoableInsertEntry(panel.getDatabase(), entry, panel));
238                     panel.getDatabase().insertEntry(entry);
239                     doLink(entry, fileType, destFilename, true, edits);
240                 } catch (KeyCollisionException ex) {
241
242                 }
243             }
244             panel.markBaseChanged();
245             panel.updateEntryEditorIfShowing();
246         }
247         return true;
248     }
249
250     public int showLinkMoveCopyRenameDialog(String dialogTitle, ExternalFileType fileType,
251         final boolean allowRename, String citekeyOrReason, boolean newEntry,
252         final boolean multipleEntries) {
253         
254         //String dir = panel.metaData().getFileDirectory(fileType.getFieldName());
255         String dir = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD);
256         if ((dir == null) || !(new File(dir)).exists()) {
257             destDirLabel.setText(Globals.lang("File directory is not set or does not exist!"));
258             copyRadioButton.setEnabled(false);
259             moveRadioButton.setEnabled(false);
260             linkInPlace.setSelected(true);
261         } else {
262             destDirLabel.setText(Globals.lang("File directory is '%0':", dir));
263             copyRadioButton.setEnabled(true);
264             moveRadioButton.setEnabled(true);
265         }
266         
267         ChangeListener cl = new ChangeListener() {
268                         public void stateChanged(ChangeEvent arg0) {
269                                 renameCheckBox.setEnabled(!linkInPlace.isSelected()
270                                                 && allowRename && (!multipleEntries));
271                         }
272                 };
273
274                 if (multipleEntries) {
275                         linkInPlace.setText(Globals
276                                         .lang("Leave files in their current directory."));
277                         copyRadioButton.setText(Globals.lang("Copy files to file directory."));
278
279                         moveRadioButton.setText(Globals.lang("Move files to file directory."));
280                 } else {
281                         linkInPlace.setText(Globals
282                                         .lang("Leave file in its current directory."));
283                         copyRadioButton.setText(Globals.lang("Copy file to file directory."));
284                         moveRadioButton.setText(Globals.lang("Move file to file directory"));
285                 }
286                 
287         renameCheckBox.setText(Globals.lang("Rename to match citekey") + ": " + citekeyOrReason);
288         linkInPlace.addChangeListener(cl);
289         cl.stateChanged(new ChangeEvent(linkInPlace));
290
291         try {
292             return JOptionPane.showConfirmDialog(frame, optionsPanel, dialogTitle,
293                 JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
294         } finally {
295             linkInPlace.removeChangeListener(cl);
296         }
297     }
298     
299     /**
300      * Make a extension to the file.
301      * 
302      * @param entry
303      *            The entry to extension from.
304      * @param fileType
305      *            The FileType associated with the file.
306      * @param filename
307      *            The path to the file.
308      * @param edits
309      *            An NamedCompound action this action is to be added to. If none
310      *            is given, the edit is added to the panel's undoManager.
311      */
312     private void doLink(BibtexEntry entry, ExternalFileType fileType, String filename,
313         boolean avoidDuplicate, NamedCompound edits) {
314
315
316         Object oldValue = entry.getField(GUIGlobals.FILE_FIELD);
317         FileListTableModel tm = new FileListTableModel();
318         if (oldValue != null)
319             tm.setContent((String)oldValue);
320
321         // If avoidDuplicate==true, we should check if this file is already linked:
322         if (avoidDuplicate) {
323             // For comparison, find the absolute filename:
324             String fileDir = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD);
325             String absFilename = (!(new File(filename).isAbsolute()) && (fileDir != null)) ?
326                     new File(fileDir, filename).getAbsolutePath() : filename;
327             System.out.println("absFilename: "+absFilename);
328             
329             for (int i=0; i<tm.getRowCount(); i++) {
330                 FileListEntry flEntry = tm.getEntry(i);
331                 // Find the absolute filename for this existing link:
332                 String absName = (!(new File(flEntry.getLink()).isAbsolute()) && (fileDir != null)) ?
333                         new File(fileDir, flEntry.getLink()).getAbsolutePath() : flEntry.getLink();
334                 System.out.println("absName: "+absName);
335                 // If the filenames are equal, we don't need to link, so we simply return:
336                 if (absFilename.equals(absName))
337                     return;
338             }
339         }
340
341         tm.addEntry(tm.getRowCount(), new FileListEntry("", filename, fileType));
342         String newValue = tm.getStringRepresentation();
343         UndoableFieldChange edit = new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD,
344                 oldValue, newValue);
345         entry.setField(GUIGlobals.FILE_FIELD, newValue);
346
347         if (edits == null) {
348             panel.undoManager.addEdit(edit);
349         } else {
350             edits.addEdit(edit);
351         }
352     }
353
354     /**
355      * Move the given file to the base directory for its file type, and rename
356      * it to the given filename.
357      * 
358      * @param fileName
359      *            The name of the source file.
360      * @param fileType
361      *            The FileType associated with the file.
362      * @param destFilename
363      *            The destination filename.
364      * @param edits
365      *            TODO we should be able to undo this action
366      * @return true if the operation succeeded.
367      */
368     private boolean doRename(String fileName, ExternalFileType fileType, String destFilename,
369         NamedCompound edits) {
370         String dir = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD);
371         if ((dir == null) || !(new File(dir)).exists()) {
372             // OOps, we don't know which directory to put it in, or the given
373             // dir doesn't exist....
374             // This should not happen!!
375             return false;
376         }
377         destFilename = new File(destFilename).getName();
378         File f = new File(fileName);
379         File destFile = new File(new StringBuffer(dir).append(System.getProperty("file.separator"))
380             .append(destFilename).toString());
381         f.renameTo(destFile);
382         return true;
383     }
384
385     /**
386      * Copy the given file to the base directory for its file type, and give it
387      * the given name.
388      * 
389      * @param fileName
390      *            The name of the source file.
391      * @param fileType
392      *            The FileType associated with the file.
393      * @param toFile
394      *            The destination filename. An existing path-component will be removed.
395      * @param edits
396      *            TODO we should be able to undo this!
397      * @return
398      */
399     private boolean doCopy(String fileName, ExternalFileType fileType, String toFile,
400         NamedCompound edits) {
401
402         String dir = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD);
403         if ((dir == null) || !(new File(dir)).exists()) {
404             // OOps, we don't know which directory to put it in, or the given
405             // dir doesn't exist....
406             System.out.println("dir: " + dir + "\t ext: " + fileType.getExtension());
407             return false;
408         }
409         toFile = new File(toFile).getName();
410         
411         File destFile = new File(new StringBuffer(dir).append(System.getProperty("file.separator"))
412             .append(toFile).toString());
413         if (destFile.equals(new File(fileName))){
414             // File is already in the correct position. Don't override!
415             return true;
416         }
417         
418         if (destFile.exists()) {
419             int answer = JOptionPane.showConfirmDialog(frame, "'" + destFile.getPath() + "' "
420                 + Globals.lang("exists.Overwrite?"), Globals.lang("File exists"),
421                 JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
422             if (answer == JOptionPane.NO_OPTION)
423                 return false;
424         }
425         try {
426             Util.copyFile(new File(fileName), destFile, true);
427         } catch (IOException e) {
428             e.printStackTrace();
429             return false;
430         }
431
432         return true;
433     }
434
435 }