1 package net.sf.jabref.gui;
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;
12 import java.awt.event.*;
14 import java.util.List;
16 import java.io.IOException;
18 import com.jgoodies.forms.builder.DefaultFormBuilder;
19 import com.jgoodies.forms.layout.FormLayout;
22 * Created by Morten O. Alver 2007.02.22
24 public class FileListEditor extends JTable implements FieldEditor,
25 DownloadExternalFile.DownloadCallback {
28 FileListEntryEditor editor = null;
29 private JabRefFrame frame;
30 private MetaData metaData;
31 private String fieldName;
32 private EntryEditor entryEditor;
34 private FileListTableModel tableModel;
35 private JScrollPane sPane;
36 private JButton add, remove, up, down, auto, download;
38 public FileListEditor(JabRefFrame frame, MetaData metaData, String fieldName, String content,
39 EntryEditor entryEditor) {
41 this.metaData = metaData;
42 this.fieldName = fieldName;
43 this.entryEditor = entryEditor;
44 label = new FieldNameLabel(" " + Util.nCase(fieldName) + " ");
45 tableModel = new FileListTableModel();
48 sPane = new JScrollPane(this);
50 addMouseListener(new TableClickListener());
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) {
67 remove.addActionListener(new ActionListener() {
68 public void actionPerformed(ActionEvent e) {
72 up.addActionListener(new ActionListener() {
73 public void actionPerformed(ActionEvent e) {
77 down.addActionListener(new ActionListener() {
78 public void actionPerformed(ActionEvent e) {
82 auto.addActionListener(new ActionListener() {
83 public void actionPerformed(ActionEvent e) {
87 download.addActionListener(new ActionListener() {
88 public void actionPerformed(ActionEvent e) {
92 DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout
93 ("fill:pref,1dlu,fill:pref,1dlu,fill:pref", "fill:pref,fill:pref"));
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);
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();
111 row = Math.min(row, getRowCount()-1);
113 setRowSelectionInterval(row, row);
117 // Add an input/action pair for inserting an entry:
118 getInputMap().put(KeyStroke.getKeyStroke("INSERT"), "insert");
119 getActionMap().put("insert", new AbstractAction() {
121 public void actionPerformed(ActionEvent actionEvent) {
129 public String getFieldName() {
134 * Returns the component to be added to a container. Might be a JScrollPane
135 * or the component itself.
137 public JComponent getPane() {
142 * Returns the text component itself.
144 public JComponent getTextComponent() {
148 public JLabel getLabel() {
152 public void setLabelColor(Color c) {
153 label.setForeground(c);
156 public String getText() {
157 return tableModel.getStringRepresentation();
160 public void setText(String newText) {
161 tableModel.setContent(newText);
165 public void append(String text) {
169 public void updateFont() {
173 public void paste(String textToInsert) {
177 public String getSelectedText() {
181 private void addEntry() {
182 int row = getSelectedRow();
185 FileListEntry entry = new FileListEntry("", "", null);
186 if (editListEntry(entry))
187 tableModel.addEntry(row, entry);
188 entryEditor.updateField(this);
191 private void removeEntries() {
192 int[] rows = getSelectedRows();
194 for (int i = rows.length-1; i>=0; i--) {
195 tableModel.removeEntry(rows[i]);
197 entryEditor.updateField(this);
200 private void moveEntry(int i) {
201 int[] sel = getSelectedRows();
202 if ((sel.length != 1) || (tableModel.getRowCount() < 2))
204 int toIdx = sel[0]+i;
205 if (toIdx >= tableModel.getRowCount())
206 toIdx -= tableModel.getRowCount();
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);
216 private boolean editListEntry(FileListEntry entry) {
217 if (editor == null) {
218 editor = new FileListEntryEditor(frame, entry, false, metaData);
221 editor.setEntry(entry);
222 editor.setVisible(true);
223 if (editor.okPressed())
224 tableModel.fireTableDataChanged();
225 entryEditor.updateField(this);
226 return editor.okPressed();
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);
238 entryEditor.updateField(FileListEditor.this);
239 frame.output(Globals.lang("Finished autosetting external links."));
241 else frame.output(Globals.lang("Finished autosetting external links.")
242 +" "+Globals.lang("No files found."));
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.
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
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
263 public static Thread autoSetLinks(final Collection<BibtexEntry> entries, final NamedCompound ce,
264 final Set<BibtexEntry> changedEntries) {
266 final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
267 final JLabel label = new JLabel(Globals.lang("Searching for files"));
268 Runnable r = new Runnable() {
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());
281 // Run the search operation:
282 Map<BibtexEntry, java.util.List<File>> result =
283 Util.findAssociatedFiles(entries, extensions, dirs);
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);
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)) {
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);
313 FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(),
314 new UnknownExternalFileType(""));
315 tableModel.addEntry(tableModel.getRowCount(), flEntry);
317 String newVal = tableModel.getStringRepresentation();
318 if (newVal.length() == 0)
320 UndoableFieldChange change = new UndoableFieldChange(anEntry,
321 GUIGlobals.FILE_FIELD, oldVal, newVal);
323 anEntry.setField(GUIGlobals.FILE_FIELD, newVal);
324 changedEntries.add(anEntry);
330 Thread t = new Thread(r);
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
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
353 public static Thread autoSetLinks(final BibtexEntry entry, final FileListTableModel tableModel,
354 final MetaData metaData, final ActionListener callback,
355 final JDialog diag) {
357 final Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>();
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"));
365 diag.setTitle(Globals.lang("Autosetting links"));
366 diag.getContentPane().add(prog, BorderLayout.CENTER);
367 diag.getContentPane().add(label, BorderLayout.SOUTH);
370 diag.setLocationRelativeTo(diag.getParent());
372 Runnable r = new Runnable() {
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());
385 // Run the search operation:
386 Map<BibtexEntry, java.util.List<File>> result =
387 Util.findAssociatedFiles(entries, extensions, dirs);
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)) {
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);
412 FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(),
413 new UnknownExternalFileType(""));
414 tableModel.addEntry(tableModel.getRowCount(), flEntry);
420 final int id = foundAny ? 1 : 0;
421 SwingUtilities.invokeLater(new Runnable() {
425 if (callback != null)
426 callback.actionPerformed(new ActionEvent(this, id, ""));
432 Thread t = new Thread(r);
435 diag.setVisible(true);
441 * If the file is below one of the directories in a list, return a File specifying
442 * a path relative to that directory.
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);
459 * Run a file download operation.
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();
474 DownloadExternalFile def = new DownloadExternalFile(frame,
475 frame.basePanel().metaData(), bibtexKey);
478 } catch (IOException ex) {
479 ex.printStackTrace();
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.
488 public void downloadComplete(FileListEntry file) {
489 tableModel.addEntry(tableModel.getRowCount(), file);
490 entryEditor.updateField(this);
493 class TableClickListener extends MouseAdapter {
495 public void mouseClicked(MouseEvent e) {
496 if ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 2)) {
497 int row = rowAtPoint(e.getPoint());
499 FileListEntry entry = tableModel.getEntry(row);
500 editListEntry(entry);