2a3dcd98478a653e7a6560431abbb9ab68215951
[debian/jabref.git] / src / java / net / sf / jabref / gui / FileListEditor.java
1 package net.sf.jabref.gui;
2
3 import net.sf.jabref.*;
4 import net.sf.jabref.undo.NamedCompound;
5 import net.sf.jabref.undo.UndoableFieldChange;
6 import net.sf.jabref.external.ExternalFileType;
7 import net.sf.jabref.external.DownloadExternalFile;
8 import net.sf.jabref.external.UnknownExternalFileType;
9
10 import javax.swing.*;
11 import java.awt.*;
12 import java.awt.event.*;
13 import java.util.*;
14 import java.util.List;
15 import java.io.File;
16 import java.io.IOException;
17
18 import com.jgoodies.forms.builder.DefaultFormBuilder;
19 import com.jgoodies.forms.layout.FormLayout;
20
21 /**
22  * Created by Morten O. Alver 2007.02.22
23  */
24 public class FileListEditor extends JTable implements FieldEditor,
25         DownloadExternalFile.DownloadCallback {
26
27     FieldNameLabel label;
28     FileListEntryEditor editor = null;
29     private JabRefFrame frame;
30     private MetaData metaData;
31     private String fieldName;
32     private EntryEditor entryEditor;
33     private JPanel panel;
34     private FileListTableModel tableModel;
35     private JScrollPane sPane;
36     private JButton add, remove, up, down, auto, download;
37
38     public FileListEditor(JabRefFrame frame, MetaData metaData, String fieldName, String content,
39                           EntryEditor entryEditor) {
40         this.frame = frame;
41         this.metaData = metaData;
42         this.fieldName = fieldName;
43         this.entryEditor = entryEditor;
44         label = new FieldNameLabel(" " + Util.nCase(fieldName) + " ");
45         tableModel = new FileListTableModel();
46         setText(content);
47         setModel(tableModel);
48         sPane = new JScrollPane(this);
49         setTableHeader(null);
50         addMouseListener(new TableClickListener());
51
52         add = new JButton(GUIGlobals.getImage("add"));
53         remove = new JButton(GUIGlobals.getImage("remove"));
54         up = new JButton(GUIGlobals.getImage("up"));
55         down = new JButton(GUIGlobals.getImage("down"));
56         auto = new JButton(Globals.lang("Auto"));
57         download = new JButton(Globals.lang("Download"));
58         add.setMargin(new Insets(0,0,0,0));
59         remove.setMargin(new Insets(0,0,0,0));
60         up.setMargin(new Insets(0,0,0,0));
61         down.setMargin(new Insets(0,0,0,0));
62         add.addActionListener(new ActionListener() {
63             public void actionPerformed(ActionEvent e) {
64                 addEntry();
65             }
66         });
67         remove.addActionListener(new ActionListener() {
68             public void actionPerformed(ActionEvent e) {
69                 removeEntries();
70             }
71         });
72         up.addActionListener(new ActionListener() {
73             public void actionPerformed(ActionEvent e) {
74                 moveEntry(-1);
75             }
76         });
77         down.addActionListener(new ActionListener() {
78             public void actionPerformed(ActionEvent e) {
79                 moveEntry(1);
80             }
81         });
82         auto.addActionListener(new ActionListener() {
83             public void actionPerformed(ActionEvent e) {
84                 autoSetLinks();
85             }
86         });
87         download.addActionListener(new ActionListener() {
88             public void actionPerformed(ActionEvent e) {
89                 downloadFile();
90             }
91         });
92         DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout
93                 ("fill:pref,1dlu,fill:pref,1dlu,fill:pref", "fill:pref,fill:pref"));
94         builder.append(up);
95         builder.append(add);
96         builder.append(auto);
97         builder.append(down);
98         builder.append(remove);
99         builder.append(download);        
100         panel = new JPanel();
101         panel.setLayout(new BorderLayout());
102         panel.add(sPane, BorderLayout.CENTER);
103         panel.add(builder.getPanel(), BorderLayout.EAST);
104
105         // Add an input/action pair for deleting entries:
106         getInputMap().put(KeyStroke.getKeyStroke("DELETE"), "delete");
107         getActionMap().put("delete", new AbstractAction() {
108             public void actionPerformed(ActionEvent actionEvent) {
109                 int row = getSelectedRow();
110                 removeEntries();
111                 row = Math.min(row, getRowCount()-1);
112                 if (row >= 0)
113                     setRowSelectionInterval(row, row);
114             }
115         });
116
117         // Add an input/action pair for inserting an entry:
118         getInputMap().put(KeyStroke.getKeyStroke("INSERT"), "insert");
119         getActionMap().put("insert", new AbstractAction() {
120
121             public void actionPerformed(ActionEvent actionEvent) {
122                 addEntry();
123             }
124         });
125     }
126
127
128
129     public String getFieldName() {
130         return fieldName;
131     }
132
133     /*
134       * Returns the component to be added to a container. Might be a JScrollPane
135     * or the component itself.
136     */
137     public JComponent getPane() {
138         return panel;
139     }
140
141     /*
142      * Returns the text component itself.
143     */
144     public JComponent getTextComponent() {
145         return this;
146     }
147
148     public JLabel getLabel() {
149         return label;
150     }
151
152     public void setLabelColor(Color c) {
153         label.setForeground(c);
154     }
155
156     public String getText() {
157         return tableModel.getStringRepresentation();
158     }
159
160     public void setText(String newText) {
161         tableModel.setContent(newText);
162     }
163
164
165     public void append(String text) {
166
167     }
168
169     public void updateFont() {
170
171     }
172
173     public void paste(String textToInsert) {
174
175     }
176
177     public String getSelectedText() {
178         return null;
179     }
180
181     private void addEntry() {
182         int row = getSelectedRow();
183         if (row == -1)
184             row = 0;
185         FileListEntry entry = new FileListEntry("", "", null);
186         if (editListEntry(entry))
187             tableModel.addEntry(row, entry);
188         entryEditor.updateField(this);
189     }
190
191     private void removeEntries() {
192         int[] rows = getSelectedRows();
193         if (rows != null)
194             for (int i = rows.length-1; i>=0; i--) {
195                 tableModel.removeEntry(rows[i]);
196             }
197         entryEditor.updateField(this);
198     }
199
200     private void moveEntry(int i) {
201         int[] sel = getSelectedRows();
202         if ((sel.length != 1) || (tableModel.getRowCount() < 2))
203             return;
204         int toIdx = sel[0]+i;
205         if (toIdx >= tableModel.getRowCount())
206             toIdx -= tableModel.getRowCount();
207         if (toIdx < 0)
208             toIdx += tableModel.getRowCount();
209         FileListEntry entry = tableModel.getEntry(sel[0]);
210         tableModel.removeEntry(sel[0]);
211         tableModel.addEntry(toIdx, entry);
212         entryEditor.updateField(this);
213         setRowSelectionInterval(toIdx, toIdx);
214     }
215
216     private boolean editListEntry(FileListEntry entry) {
217         if (editor == null) {
218             editor = new FileListEntryEditor(frame, entry, false, metaData);
219         }
220         else
221             editor.setEntry(entry);
222         editor.setVisible(true);
223         if (editor.okPressed())
224             tableModel.fireTableDataChanged();
225         entryEditor.updateField(this);
226         return editor.okPressed();
227     }
228
229     private void autoSetLinks() {
230         auto.setEnabled(false);
231         BibtexEntry entry = entryEditor.getEntry();
232         int tableSize = tableModel.getRowCount();
233         JDialog diag = new JDialog(frame, true);
234         autoSetLinks(entry, tableModel, metaData, new ActionListener() {
235             public void actionPerformed(ActionEvent e) {
236                 auto.setEnabled(true);
237                 if (e.getID() > 0) {
238                     entryEditor.updateField(FileListEditor.this);
239                     frame.output(Globals.lang("Finished autosetting external links."));
240                 }
241                 else frame.output(Globals.lang("Finished autosetting external links.")
242                     +" "+Globals.lang("No files found."));
243             }
244         }, diag);
245
246     }
247
248     /**
249      * Automatically add links for this set of entries, based on the globally stored list of
250      * external file types. The entries are modified, and corresponding UndoEdit elements
251      * added to the NamedCompound given as argument. Furthermore, all entries which are modified
252      * are added to the Set of entries given as an argument.
253      *
254      * The entries' bibtex keys must have been set - entries lacking key are ignored.
255      * The operation is done in a new thread, which is returned for the caller to wait for
256      * if needed.
257      *
258      * @param entries A collection of BibtexEntry objects to find links for.
259      * @param ce A NamedCompound to add UndoEdit elements to.
260      * @param changedEntries A Set of BibtexEntry objects to which all modified entries is added.
261      * @return the thread performing the autosetting
262      */
263     public static Thread autoSetLinks(final Collection<BibtexEntry> entries, final NamedCompound ce,
264                                       final Set<BibtexEntry> changedEntries) {
265
266         final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
267         final JLabel label = new JLabel(Globals.lang("Searching for files"));
268         Runnable r = new Runnable() {
269
270             public void run() {
271                 boolean foundAny = false;
272                 ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
273                 ArrayList<File> dirs = new ArrayList<File>();
274                 if (Globals.prefs.hasKey(GUIGlobals.FILE_FIELD + "Directory"))
275                     dirs.add(new File(Globals.prefs.get(GUIGlobals.FILE_FIELD + "Directory")));
276                 Collection<String> extensions = new ArrayList<String>();
277                 for (int i = 0; i < types.length; i++) {
278                     final ExternalFileType type = types[i];
279                     extensions.add(type.getExtension());
280                 }
281                 // Run the search operation:
282                 Map<BibtexEntry, java.util.List<File>> result =
283                         Util.findAssociatedFiles(entries, extensions, dirs);
284
285                 // Iterate over the entries:
286                 for (Iterator<BibtexEntry> i=result.keySet().iterator(); i.hasNext();) {
287                     BibtexEntry anEntry = i.next();
288                     FileListTableModel tableModel = new FileListTableModel();
289                     Object oldVal = anEntry.getField(GUIGlobals.FILE_FIELD);
290                     if (oldVal != null)
291                         tableModel.setContent((String)oldVal);
292                     List<File> files = result.get(anEntry);
293                     for (File f : files) {
294                         f = relativizePath(f, dirs);
295                         boolean alreadyHas = false;
296                         //System.out.println("File: "+f.getPath());
297                         for (int j = 0; j < tableModel.getRowCount(); j++) {
298                             FileListEntry existingEntry = tableModel.getEntry(j);
299                             //System.out.println("Comp: "+existingEntry.getLink());
300                             if (new File(existingEntry.getLink()).equals(f)) {
301                                 alreadyHas = true;
302                                 break;
303                             }
304                         }
305                         if (!alreadyHas) {
306                             int index = f.getPath().lastIndexOf('.');
307                             if ((index >= 0) && (index < f.getPath().length()-1)) {
308                                 ExternalFileType type = Globals.prefs.getExternalFileTypeByExt
309                                     (f.getPath().substring(index+1));
310                                 FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(), type);
311                                 tableModel.addEntry(tableModel.getRowCount(), flEntry);
312                             } else {
313                                 FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(),
314                                         new UnknownExternalFileType(""));
315                                 tableModel.addEntry(tableModel.getRowCount(), flEntry);
316                             }
317                             String newVal = tableModel.getStringRepresentation();
318                             if (newVal.length() == 0)
319                                 newVal = null;
320                             UndoableFieldChange change = new UndoableFieldChange(anEntry,
321                                     GUIGlobals.FILE_FIELD, oldVal, newVal);
322                             ce.addEdit(change);
323                             anEntry.setField(GUIGlobals.FILE_FIELD, newVal);
324                             changedEntries.add(anEntry);
325                         }
326                     }
327                 }
328             }
329         };
330         Thread t = new Thread(r);
331         t.start();
332         return t;
333     }
334
335
336     /**
337      * Automatically add links for this entry to the table model given as an argument, based on
338      * the globally stored list of external file types. The entry itself is not modified. The entry's
339      * bibtex key must have been set.
340      * The operation is done in a new thread, which is returned for the caller to wait for
341      * if needed.
342      *
343      * @param entry The BibtexEntry to find links for.
344      * @param tableModel The table model to insert links into. Already existing links are not duplicated or removed.
345      * @param metaData The MetaData providing the relevant file directory, if any.
346      * @param callback An ActionListener that is notified (on the event dispatch thread) when the search is
347      *  finished. The ActionEvent has id=0 if no new links were added, and id=1 if one or more links were added.
348      *  This parameter can be null, which means that no callback will be notified.
349      * @param diag An instantiated modal JDialog which will be used to display the progress of the autosetting.
350      *      This parameter can be null, which means that no progress update will be shown.
351      * @return the thread performing the autosetting
352      */
353     public static Thread autoSetLinks(final BibtexEntry entry, final FileListTableModel tableModel,
354                                       final MetaData metaData, final ActionListener callback,
355                                       final JDialog diag) {
356
357         final Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>();
358         entries.add(entry);
359         final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
360         final JProgressBar prog = new JProgressBar(JProgressBar.HORIZONTAL, types.length-1);
361         prog.setIndeterminate(true);
362         prog.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
363         final JLabel label = new JLabel(Globals.lang("Searching for files"));
364         if (diag != null) {
365             diag.setTitle(Globals.lang("Autosetting links"));
366             diag.getContentPane().add(prog, BorderLayout.CENTER);
367             diag.getContentPane().add(label, BorderLayout.SOUTH);
368
369             diag.pack();
370             diag.setLocationRelativeTo(diag.getParent());
371         }
372         Runnable r = new Runnable() {
373
374             public void run() {
375                 boolean foundAny = false;
376                 ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
377                 ArrayList<File> dirs = new ArrayList<File>();
378                 if (metaData.getFileDirectory(GUIGlobals.FILE_FIELD) != null)
379                     dirs.add(new File(metaData.getFileDirectory(GUIGlobals.FILE_FIELD)));
380                 Collection<String> extensions = new ArrayList<String>();
381                 for (int i = 0; i < types.length; i++) {
382                     final ExternalFileType type = types[i];
383                     extensions.add(type.getExtension());
384                 }
385                 // Run the search operation:
386                 Map<BibtexEntry, java.util.List<File>> result =
387                         Util.findAssociatedFiles(entries, extensions, dirs);
388
389                 // Iterate over the entries:
390                 for (Iterator<BibtexEntry> i=result.keySet().iterator(); i.hasNext();) {
391                     BibtexEntry anEntry = i.next();
392                     List<File> files = result.get(anEntry);
393                     for (File f : files) {
394                                 f = relativizePath(f, dirs);
395                         boolean alreadyHas = false;
396                         for (int j = 0; j < tableModel.getRowCount(); j++) {
397                             FileListEntry existingEntry = tableModel.getEntry(j);
398                             if (new File(existingEntry.getLink()).equals(f)) {
399                                 alreadyHas = true;
400                                 break;
401                             }
402                         }
403                         if (!alreadyHas) {
404                             int index = f.getPath().lastIndexOf('.');
405                             if ((index >= 0) && (index < f.getPath().length()-1)) {
406                                 ExternalFileType type = Globals.prefs.getExternalFileTypeByExt
407                                     (f.getPath().substring(index+1));
408                                 FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(), type);
409                                 tableModel.addEntry(tableModel.getRowCount(), flEntry);
410                                 foundAny = true;
411                             } else {
412                                 FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(),
413                                         new UnknownExternalFileType(""));
414                                 tableModel.addEntry(tableModel.getRowCount(), flEntry);
415                                 foundAny = true;
416                             }
417                         }
418                     }
419                 }
420                 final int id = foundAny ? 1 : 0;
421                 SwingUtilities.invokeLater(new Runnable() {
422                     public void run() {
423                         if (diag != null)
424                             diag.dispose();
425                         if (callback != null)
426                             callback.actionPerformed(new ActionEvent(this, id, ""));
427                     }
428                 });
429
430             }
431         };
432         Thread t = new Thread(r);
433         t.start();
434         if (diag != null) {
435             diag.setVisible(true);
436         }
437         return t;
438     }
439
440     /**
441      * If the file is below one of the directories in a list, return a File specifying
442      * a path relative to that directory.
443      */
444     public static File relativizePath(File f, ArrayList<File> dirs) {
445         String pth = f.getPath();
446         for (File dir : dirs) {
447             if (pth.startsWith(dir.getPath())) {
448                 String subs = pth.substring(dir.getPath().length());
449                 if ((subs.length() > 0) && ((subs.charAt(0) == '/') || (subs.charAt(0) == '\\')))
450                     subs = subs.substring(1);
451             return new File(subs);          
452             }
453         }
454         return f;
455     }
456
457
458     /**
459      * Run a file download operation.
460      */
461     private void downloadFile() {
462         String bibtexKey = entryEditor.getEntry().getCiteKey();
463         if (bibtexKey == null) {
464             int answer = JOptionPane.showConfirmDialog(frame,
465                     Globals.lang("This entry has no BibTeX key. Generate key now?"),
466                     Globals.lang("Download file"), JOptionPane.OK_CANCEL_OPTION,
467                     JOptionPane.QUESTION_MESSAGE);
468             if (answer == JOptionPane.OK_OPTION) {
469                 ActionListener l = entryEditor.generateKeyAction;
470                 l.actionPerformed(null);
471                 bibtexKey = entryEditor.getEntry().getCiteKey();
472             }
473         }
474         DownloadExternalFile def = new DownloadExternalFile(frame,
475                 frame.basePanel().metaData(), bibtexKey);
476         try {
477             def.download(this);
478         } catch (IOException ex) {
479             ex.printStackTrace();
480         }
481     }
482
483     /**
484      * This is the callback method that the DownloadExternalFile class uses to report the result
485      * of a download operation. This call may never come, if the user cancelled the operation.
486      * @param file The FileListEntry linking to the resulting local file.
487      */
488     public void downloadComplete(FileListEntry file) {
489         tableModel.addEntry(tableModel.getRowCount(), file);
490         entryEditor.updateField(this);
491     }
492
493     class TableClickListener extends MouseAdapter {
494
495         public void mouseClicked(MouseEvent e) {
496             if ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 2)) {
497                 int row = rowAtPoint(e.getPoint());
498                 if (row >= 0) {
499                     FileListEntry entry = tableModel.getEntry(row);
500                     editListEntry(entry);
501                 }
502             }
503         }
504     }
505
506
507 }