0c7848aaac0c263624aac64068072f72cf533fea
[debian/jabref.git] / src / java / net / sf / jabref / external / ExternalFilePanel.java
1 package net.sf.jabref.external;
2
3 import java.awt.Component;
4 import java.awt.GridLayout;
5 import java.awt.dnd.DnDConstants;
6 import java.awt.dnd.DropTarget;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.io.File;
10 import java.io.IOException;
11 import java.net.MalformedURLException;
12 import java.net.URL;
13 import java.util.LinkedList;
14
15 import javax.swing.JButton;
16 import javax.swing.JComponent;
17 import javax.swing.JFileChooser;
18 import javax.swing.JOptionPane;
19 import javax.swing.JPanel;
20 import javax.swing.SwingUtilities;
21 import javax.xml.transform.TransformerException;
22
23 import net.sf.jabref.BibtexDatabase;
24 import net.sf.jabref.BibtexEntry;
25 import net.sf.jabref.BibtexFields;
26 import net.sf.jabref.EntryEditor;
27 import net.sf.jabref.FieldEditor;
28 import net.sf.jabref.Globals;
29 import net.sf.jabref.JabRefFrame;
30 import net.sf.jabref.MetaData;
31 import net.sf.jabref.OpenFileFilter;
32 import net.sf.jabref.UrlDragDrop;
33 import net.sf.jabref.Util;
34 import net.sf.jabref.net.URLDownload;
35 import net.sf.jabref.util.XMPUtil;
36
37 /**
38  * Initial Version:
39  * 
40  * @author alver
41  * @version Date: May 7, 2005 Time: 7:17:42 PM
42  * 
43  * Current Version:
44  * 
45  * @author $Author: coezbek $
46  * @version $Revision: 2110 $ ($Date: 2007-06-13 03:39:51 +0200 (Wed, 13 Jun 2007) $)
47  * 
48  */
49 public class ExternalFilePanel extends JPanel {
50
51         private static final long serialVersionUID = 3653290879640642718L;
52
53         private JButton browseBut, download, auto, xmp;
54
55         private EntryEditor entryEditor;
56
57     private FieldEditor fieldEditor;
58
59     private JabRefFrame frame;
60
61         private OpenFileFilter off;
62
63         private BibtexEntry entry;
64         
65         private BibtexDatabase database;
66
67         private MetaData metaData;
68
69         public ExternalFilePanel(final String fieldName, final MetaData metaData,
70                 final BibtexEntry entry, final FieldEditor editor, final OpenFileFilter off) {
71                 this(null, metaData, null, fieldName, off, null);
72                 this.entry = entry;
73         this.entryEditor = null;
74         this.fieldEditor = editor;
75     }
76
77         public ExternalFilePanel(final JabRefFrame frame, final MetaData metaData,
78                 final EntryEditor entryEditor, final String fieldName, final OpenFileFilter off,
79                 final FieldEditor editor) {
80
81                 this.frame = frame;
82                 this.metaData = metaData;
83                 this.off = off;
84                 this.entryEditor = entryEditor;
85         this.fieldEditor = null;
86
87         setLayout(new GridLayout(2, 2));
88
89                 browseBut = new JButton(Globals.lang("Browse"));
90                 download = new JButton(Globals.lang("Download"));
91                 auto = new JButton(Globals.lang("Auto"));
92                 xmp = new JButton(Globals.lang("Write XMP"));
93                 xmp.setToolTipText(Globals.lang("Write BibtexEntry as XMP-metadata to PDF."));
94
95                 browseBut.addActionListener(new ActionListener() {
96                         public void actionPerformed(ActionEvent e) {
97                                 browseFile(fieldName, editor);
98                                 // editor.setText(chosenValue);
99                                 entryEditor.storeFieldAction.actionPerformed(new ActionEvent(editor, 0, ""));
100                         }
101                 });
102
103                 download.addActionListener(new ActionListener() {
104                         public void actionPerformed(ActionEvent e) {
105                                 downLoadFile(fieldName, editor, frame);
106                         }
107                 });
108
109                 auto.addActionListener(new ActionListener() {
110                         public void actionPerformed(ActionEvent e) {
111                                 autoSetFile(fieldName, editor);
112                         }
113                 });
114                 xmp.addActionListener(new ActionListener() {
115                         public void actionPerformed(ActionEvent e) {
116                                 pushXMP(fieldName, editor);
117                         }
118                 });
119
120                 add(browseBut);
121                 add(download);
122                 add(auto);
123                 add(xmp);
124
125                 // Add drag and drop support to the field
126                 if (editor != null)
127                         ((JComponent) editor).setDropTarget(new DropTarget((Component) editor,
128                                 DnDConstants.ACTION_NONE, new UrlDragDrop(entryEditor, frame, editor)));
129         }
130
131         /**
132          * Change which entry this panel is operating on. This is used only when
133          * this panel is not attached to an entry editor.
134          */
135         public void setEntry(BibtexEntry entry, BibtexDatabase database) {
136                 this.entry = entry;
137                 this.database = database;
138         }
139         
140         public BibtexDatabase getDatabase(){
141                 return (database != null ? database : entryEditor.getDatabase());
142         }
143
144         public BibtexEntry getEntry() {
145                 return (entry != null ? entry : entryEditor.getEntry());
146         }
147
148         protected Object getKey() {
149                 return getEntry().getField(BibtexFields.KEY_FIELD);
150         }
151
152         protected void output(String s) {
153                 if (frame != null)
154                         frame.output(s);
155         }
156
157         public void pushXMP(final String fieldName, final FieldEditor editor) {
158
159
160                 (new Thread() {
161                         public void run() {
162
163                                 output(Globals.lang("Looking for pdf..."));
164                                 
165                                 // Find the default directory for this field type, if any:
166                                 String dir = metaData.getFileDirectory(fieldName);
167                                 File file = null;
168                                 if (dir != null) {
169                                         File tmp = Util.expandFilename(editor.getText(), new String[] { dir, "." });
170                                         if (tmp != null)
171                                                 file = tmp;
172                                 }
173
174                                 if (file == null) {
175                                         file = new File(editor.getText());
176                                 }
177
178                                 if (file == null) {
179                                         output(Globals.lang("No file associated"));
180                                         return;
181                                 }
182                                 
183                                 final File finalFile = file;
184
185                 
186                                 output(Globals.lang("Writing XMP to '%0'...", finalFile.getName()));
187                                 try {
188                                         XMPUtil.writeXMP(finalFile, getEntry(), getDatabase());
189                                         output(Globals.lang("Wrote XMP to '%0'.", finalFile.getName()));
190                                 } catch (IOException e) {
191                                         JOptionPane.showMessageDialog(editor.getParent(), Globals.lang(
192                                                 "Error writing XMP to file: %0", e.getLocalizedMessage()), Globals
193                                                 .lang("Writing XMP"), JOptionPane.ERROR_MESSAGE);
194                                         Globals.logger(Globals.lang("Error while writing XMP %0", finalFile
195                                                 .getAbsolutePath()));
196                                         output(Globals.lang("Error writing XMP to '%0'...", finalFile.getName()));
197                                         
198                                 } catch (TransformerException e) {
199                                         JOptionPane.showMessageDialog(editor.getParent(), Globals.lang(
200                                                 "Error converting Bibtex to XMP: %0", e.getLocalizedMessage()), Globals
201                                                 .lang("Writing XMP"), JOptionPane.ERROR_MESSAGE);
202                                         Globals.logger(Globals.lang("Error while converting BibtexEntry to XMP %0",
203                                                 finalFile.getAbsolutePath()));
204                                         output(Globals.lang("Error converting XMP to '%0'...", finalFile.getName()));
205                                 }
206                         }
207                 }).start();
208         }
209
210         public void browseFile(final String fieldName, final FieldEditor editor) {
211
212                 String directory = metaData.getFileDirectory(fieldName);
213                 if ((directory != null) && directory.equals(""))
214                         directory = null;
215
216                 String dir = editor.getText(), retVal = null;
217
218                 if ((directory == null) || !(new File(dir)).isAbsolute()) {
219                         if (directory != null)
220                                 dir = directory;
221                         else
222                                 dir = Globals.prefs.get(fieldName + Globals.FILETYPE_PREFS_EXT, "");
223                 }
224
225                 String chosenFile = Globals.getNewFile(frame, new File(dir), "." + fieldName,
226                         JFileChooser.OPEN_DIALOG, false);
227
228                 if (chosenFile != null) {
229                         File newFile = new File(chosenFile);
230                         String position = newFile.getParent();
231
232                         if ((directory != null) && position.startsWith(directory)) {
233                                 // Construct path relative to pdf base dir
234                                 String relPath = position.substring(directory.length(), position.length())
235                                         + File.separator + newFile.getName();
236
237                                 // Remove leading path separator
238                                 if (relPath.startsWith(File.separator)) {
239                                         relPath = relPath.substring(File.separator.length(), relPath.length());
240
241                                         // Set relative path as field value
242                                 }
243
244                                 retVal = relPath;
245                         } else
246                                 retVal = newFile.getPath();
247
248                         editor.setText(retVal);
249                         Globals.prefs.put(fieldName + Globals.FILETYPE_PREFS_EXT, newFile.getPath());
250                 }
251         }
252
253         public void downLoadFile(final String fieldName, final FieldEditor fieldEditor,
254                 final Component parent) {
255
256                 final String res = JOptionPane.showInputDialog(parent, Globals
257                         .lang("Enter URL to download"));
258
259                 if (res == null || res.trim().length() == 0)
260                         return;
261
262                 /*
263                  * If this panel belongs in an entry editor, note which entry is
264                  * currently shown:
265                  */
266                 final BibtexEntry targetEntry;
267                 if (entryEditor != null)
268                         targetEntry = entryEditor.getEntry();
269                 else
270                         targetEntry = entry;
271
272                 (new Thread() {
273
274                         public String getPlannedFileName(String res) {
275                                 String suffix = off.getSuffix(res);
276                                 if (suffix == null)
277                                         suffix = "." + fieldName.toLowerCase();
278
279                                 String plannedName = null;
280                                 if (getKey() != null)
281                                         plannedName = getKey() + suffix;
282                                 else {
283                                         plannedName = JOptionPane.showInputDialog(parent, Globals
284                                                 .lang("BibTeX key not set. Enter a name for the downloaded file"));
285                                         if (plannedName != null && !off.accept(plannedName))
286                                                 plannedName += suffix;
287                                 }
288
289                                 /*
290                                  * [ 1548875 ] download pdf produces unsupported filename
291                                  * 
292                                  * http://sourceforge.net/tracker/index.php?func=detail&aid=1548875&group_id=92314&atid=600306
293                                  * 
294                                  */
295                                 if (Globals.ON_WIN) {
296                                         plannedName = plannedName.replaceAll(
297                                                 "\\?|\\*|\\<|\\>|\\||\\\"|\\:|\\.$|\\[|\\]", "");
298                                 } else if (Globals.ON_MAC) {
299                                         plannedName = plannedName.replaceAll(":", "");
300                                 }
301
302                                 return plannedName;
303                         }
304
305                         public void run() {
306                                 String originalText = fieldEditor.getText();
307                                 fieldEditor.setEnabled(false);
308                                 boolean updateEditor = true;
309
310                                 try {
311                                         fieldEditor.setText(Globals.lang("Downloading..."));
312                                         output(Globals.lang("Downloading..."));
313                                         String plannedName = getPlannedFileName(res);
314
315                                         // Find the default directory for this field type:
316                                         String directory = metaData.getFileDirectory(fieldName);
317
318                                         if (!new File(directory).exists()) {
319                                                 JOptionPane.showMessageDialog(parent, Globals.lang(
320                                                         "Could not find directory for %0-files: %1", fieldName, directory),
321                                                         Globals.lang("Download file"), JOptionPane.ERROR_MESSAGE);
322                                                 Globals.logger(Globals.lang("Could not find directory for %0-files: %1",
323                                                         fieldName, directory));
324                                                 return;
325                                         }
326                                         File file = new File(new File(directory), plannedName);
327
328                                         URL url = new URL(res);
329
330                                         URLDownload udl = new URLDownload(parent, url, file);
331                                         try {
332                                                 udl.download();
333                                         } catch (IOException e2) {
334                                                 JOptionPane.showMessageDialog(parent, Globals.lang("Invalid URL: "
335                                                         + e2.getMessage()), Globals.lang("Download file"),
336                                                         JOptionPane.ERROR_MESSAGE);
337                                                 Globals.logger("Error while downloading " + url.toString());
338                                                 return;
339                                         }
340                                         output(Globals.lang("Download completed"));
341
342                                         String textToSet = file.getPath();
343                                         if (textToSet.startsWith(directory)) {
344                                                 // Construct path relative to pdf base dir
345                                                 textToSet = textToSet.substring(directory.length(), textToSet.length());
346
347                                                 // Remove leading path separator
348                                                 if (textToSet.startsWith(File.separator)) {
349                                                         textToSet = textToSet.substring(File.separator.length());
350                                                 }
351                                         }
352
353                                         /*
354                                          * Check if we should update the editor text field, or
355                                          * update the target entry directly:
356                                          */
357                     if (entryEditor == null || entryEditor.getEntry() != targetEntry) {
358                                                 /*
359                                                  * Editor has probably changed to show a different
360                                                  * entry. So we must update the target entry directly
361                                                  * and not set the text of the editor.
362                                                  */
363                                                 targetEntry.setField(fieldName, textToSet);
364                         if (fieldEditor != null) {
365                             fieldEditor.setText(textToSet);
366                             fieldEditor.setEnabled(true);
367                         }
368                         updateEditor = false;
369                                         } else {
370                                                 /*
371                                                  * Need to set the fieldEditor first before running
372                                                  * updateField-Action, because otherwise we might get a
373                                                  * race condition.
374                                                  * 
375                                                  * (Hopefully a) Fix for: [ 1545601 ] downloading pdf
376                                                  * corrupts pdf field text
377                                                  * 
378                                                  * http://sourceforge.net/tracker/index.php?func=detail&aid=1545601&group_id=92314&atid=600306
379                                                  */
380                                                 fieldEditor.setText(textToSet);
381                                                 fieldEditor.setEnabled(true);
382                                                 updateEditor = false;
383                                                 SwingUtilities.invokeLater(new Thread() {
384                                                         public void run() {
385                                                                 entryEditor.updateField(fieldEditor);
386                                                         }
387                                                 });
388                                         }
389
390                                 } catch (MalformedURLException e1) {
391                                         JOptionPane.showMessageDialog(parent, Globals.lang("Invalid URL"), Globals
392                                                 .lang("Download file"), JOptionPane.ERROR_MESSAGE);
393                                 } finally {
394                                         // If stuff goes wrong along the road, put back original
395                                         // value
396                                         if (updateEditor) {
397                                                 fieldEditor.setText(originalText);
398                                                 fieldEditor.setEnabled(true);
399                                         }
400                 }
401                         }
402                 }).start();
403         }
404
405         /**
406          * Starts a thread that searches the external file directory for the given
407          * field name, including subdirectories, and looks for files named after the
408          * current entry's bibtex key. Returns a reference to the thread for callers
409          * that may want to wait for the thread to finish (using join()).
410          * 
411          * @param fieldName
412          *            The field to set.
413          * @param editor
414          *            An EntryEditor instance where to set the value found.
415          * @return A reference to the Thread that performs the operation.
416          */
417         public Thread autoSetFile(final String fieldName, final FieldEditor editor) {
418                 Object o = getKey();
419                 if ((o == null) || (Globals.prefs.get(fieldName + "Directory") == null)) {
420                         output(Globals.lang("You must set both BibTeX key and %0 directory", fieldName
421                                 .toUpperCase())
422                                 + ".");
423                         return null;
424                 }
425                 output(Globals.lang("Searching for %0 file", fieldName.toUpperCase()) + " '" + o + "."
426                         + fieldName + "'...");
427                 Thread t = (new Thread() {
428                         public void run() {
429                                 /*
430                                  * Find the following directories to look in for:
431                                  * 
432                                  * default directory for this field type.
433                                  * 
434                                  * directory of bibtex-file. // NOT POSSIBLE at the moment.
435                                  * 
436                                  * JabRef-directory.
437                                  */
438                                 LinkedList<String> list = new LinkedList<String>();
439                                 list.add(metaData.getFileDirectory(fieldName));
440
441                                 /*
442                                  * File fileOfDb = frame.basePanel().file(); if (fileOfDb !=
443                                  * null){ list.add(fileOfDb.getParentFile().getPath()); }
444                                  */
445                                 list.add(".");
446
447                                 String found = Util.findPdf(getEntry(), fieldName, (String[]) list
448                                         .toArray(new String[list.size()]));// , off);
449                                         
450                                 
451                                 // To activate findFile:
452                                 // String found = Util.findFile(getEntry(), null, dir,
453                                 // ".*[bibtexkey].*");
454
455                                 if (found != null) {
456                                         editor.setText(found);
457                                         if (entryEditor != null)
458                                                 entryEditor.updateField(editor);
459                                         output(Globals.lang("%0 field set", fieldName.toUpperCase()) + ".");
460                                 } else {
461                                         output(Globals.lang("No %0 found", fieldName.toUpperCase()) + ".");
462                                 }
463
464                         }
465                 });
466
467                 t.start();
468                 return t;
469
470         }
471
472 }