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