b24e9d74afcad17eb57851b75fb12c4caf0c8732
[debian/jabref.git] / src / java / net / sf / jabref / gui / ImportInspectionDialog.java
1 package net.sf.jabref.gui;
2
3 import net.sf.jabref.*;
4 import net.sf.jabref.external.DownloadExternalFile;
5 import net.sf.jabref.external.ExternalFileMenuItem;
6 import net.sf.jabref.groups.GroupTreeNode;
7 import net.sf.jabref.groups.AllEntriesGroup;
8 import net.sf.jabref.groups.AbstractGroup;
9 import net.sf.jabref.groups.UndoableChangeAssignment;
10 import net.sf.jabref.labelPattern.LabelPatternUtil;
11 import net.sf.jabref.undo.NamedCompound;
12 import net.sf.jabref.undo.UndoableInsertEntry;
13 import net.sf.jabref.undo.UndoableRemoveEntry;
14
15 import javax.swing.*;
16 import javax.swing.undo.AbstractUndoableEdit;
17 import javax.swing.event.ListSelectionListener;
18 import javax.swing.event.ListSelectionEvent;
19 import javax.swing.table.*;
20 import java.util.*;
21 import java.util.List;
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.io.IOException;
25
26 import com.jgoodies.forms.builder.ButtonBarBuilder;
27 import com.jgoodies.forms.builder.ButtonStackBuilder;
28 import com.jgoodies.uif_lite.component.UIFSplitPane;
29 import ca.odell.glazedlists.*;
30 import ca.odell.glazedlists.event.ListEventListener;
31 import ca.odell.glazedlists.event.ListEvent;
32 import ca.odell.glazedlists.gui.TableFormat;
33 import ca.odell.glazedlists.swing.TableComparatorChooser;
34 import ca.odell.glazedlists.swing.EventTableModel;
35 import ca.odell.glazedlists.swing.EventSelectionModel;
36
37
38 /**
39  * Dialog to allow the selection of entries as part of an Import
40  * 
41  * @author alver
42  * @author $Author: mortenalver $
43  * @version $Revision: 2279 $ ($Date: 2007-08-21 19:30:22 +0200 (Tue, 21 Aug 2007) $)
44  *
45  */
46 public class ImportInspectionDialog extends JDialog {
47         
48     private ImportInspectionDialog ths = this;
49     private BasePanel panel;
50     private JabRefFrame frame;
51     private MetaData metaData;
52     private UIFSplitPane contentPane = new UIFSplitPane(UIFSplitPane.VERTICAL_SPLIT);
53     private JTable glTable;
54     private TableComparatorChooser comparatorChooser;
55     private EventSelectionModel selectionModel;
56     private String[] fields;
57     private JProgressBar progressBar = new JProgressBar(JProgressBar.HORIZONTAL);
58     private JButton ok = new JButton(Globals.lang("Ok")),
59         cancel = new JButton(Globals.lang("Cancel")),
60         generate = new JButton(Globals.lang("Generate now"));
61     private EventList entries = new BasicEventList();
62     private SortedList sortedList;
63     private List entriesToDelete = new ArrayList(); // Duplicate resolving may require deletion of old entries.
64     private String undoName;
65     private ArrayList callBacks = new ArrayList();
66     private boolean newDatabase;
67     private JMenu groupsAdd = new JMenu(Globals.lang("Add to group"));
68     private JPopupMenu popup = new JPopupMenu();
69     private JButton selectAll = new JButton(Globals.lang("Select all"));
70     private JButton deselectAll = new JButton(Globals.lang("Deselect all"));
71     private JButton deselectAllDuplicates = new JButton(Globals.lang("Deselect all duplicates"));
72     private JButton stop = new JButton(Globals.lang("Stop"));
73     private JButton delete = new JButton(Globals.lang("Delete"));
74     private JButton help = new JButton(Globals.lang("Help"));
75     private PreviewPanel preview;
76     private boolean generatedKeys = false; // Set to true after keys have been generated.
77     private boolean defaultSelected = true;
78     private Rectangle toRect = new Rectangle(0, 0, 1, 1);
79     private Map groupAdditions = new HashMap();
80     private JCheckBox autoGenerate = new JCheckBox(Globals.lang("Generate keys"), Globals.prefs.getBoolean("generateKeysAfterInspection"));
81     private JLabel
82         duplLabel = new JLabel(GUIGlobals.getImage("duplicate")),
83         fileLabel = new JLabel(GUIGlobals.getImage("psSmall")),
84         pdfLabel = new JLabel(GUIGlobals.getImage("pdfSmall")),
85         psLabel = new JLabel(GUIGlobals.getImage("psSmall")),
86         urlLabel = new JLabel(GUIGlobals.getImage("wwwSmall"));
87
88     private final int
89         DUPL_COL = 1,
90         FILE_COL = 2,
91         PDF_COL = -1,//3,
92         PS_COL = -2,//4,
93         URL_COL = 3,//5,
94         PAD = 4; // 6;
95
96     /**
97      * The "defaultSelected" boolean value determines if new entries added are selected for import or not.
98      * This value is true by default.
99      * @param defaultSelected The desired value.
100      */
101     public void setDefaultSelected(boolean defaultSelected) {
102         this.defaultSelected = defaultSelected;
103     }
104
105     /**
106      * Creates a dialog that displays the given list of fields in the table.
107      * The dialog allows another process to add entries dynamically while the dialog
108      * is shown.
109      *
110      * @param frame
111      * @param panel
112      * @param fields
113      */
114     public ImportInspectionDialog(JabRefFrame frame, BasePanel panel, String[] fields,
115                                   String undoName, boolean newDatabase) {
116         this.frame = frame;
117         this.panel = panel;
118         this.metaData = (panel != null) ? panel.metaData() : new MetaData();
119         this.fields = fields;
120         this.undoName = undoName;
121         this.newDatabase = newDatabase;
122         preview = new PreviewPanel(metaData, Globals.prefs.get("preview1"));
123
124         duplLabel.setToolTipText(Globals.lang("Possible duplicate of existing entry. Click to resolve."));
125
126         sortedList = new SortedList(entries);
127         EventTableModel tableModelGl = new EventTableModel(sortedList,
128                 new EntryTableFormat());
129         glTable = new EntryTable(tableModelGl);
130         GeneralRenderer renderer = new GeneralRenderer(Color.white);
131         glTable.setDefaultRenderer(JLabel.class, renderer);
132         glTable.setDefaultRenderer(String.class, renderer);
133         glTable.getInputMap().put(Globals.prefs.getKey("Delete"), "delete");
134         DeleteListener deleteListener = new DeleteListener();
135         glTable.getActionMap().put("delete", deleteListener);
136
137         selectionModel = new EventSelectionModel(sortedList);
138         glTable.setSelectionModel(selectionModel);
139         selectionModel.getSelected().addListEventListener(new EntrySelectionListener());
140         comparatorChooser = new TableComparatorChooser(glTable, sortedList,
141                 TableComparatorChooser.MULTIPLE_COLUMN_KEYBOARD);
142         setupComparatorChooser();
143         glTable.addMouseListener(new TableClickListener());
144
145         setWidths();
146
147         getContentPane().setLayout(new BorderLayout());
148         progressBar.setIndeterminate(true);
149         JPanel centerPan = new JPanel();
150         centerPan.setLayout(new BorderLayout());
151
152         contentPane.setTopComponent(new JScrollPane(glTable));
153         contentPane.setBottomComponent(new JScrollPane(preview));
154
155         centerPan.add(contentPane, BorderLayout.CENTER);
156         centerPan.add(progressBar, BorderLayout.SOUTH);
157
158         popup.add(deleteListener);
159         popup.addSeparator();
160         if (!newDatabase) {
161             GroupTreeNode node = metaData.getGroups();
162             groupsAdd.setEnabled(false); // Will get enabled if there are groups that can be added to.
163             insertNodes(groupsAdd, node, true);
164             popup.add(groupsAdd);
165         }
166
167         // Add "Attach file" menu choices to right click menu:
168         popup.add(new LinkLocalFile());
169         popup.add(new DownloadFile());
170         popup.add(new AutoSetLinks());
171         //popup.add(new AttachFile("pdf"));
172         //popup.add(new AttachFile("ps"));
173         popup.add(new AttachUrl());
174         getContentPane().add(centerPan, BorderLayout.CENTER);
175
176
177         ButtonBarBuilder bb = new ButtonBarBuilder();
178         bb.addGlue();
179         bb.addGridded(ok);
180         bb.addGridded(stop);
181         bb.addGridded(cancel);
182         bb.addRelatedGap();
183         bb.addGridded(help);
184         bb.addGlue();
185         bb.getPanel().setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
186
187         ButtonStackBuilder builder = new ButtonStackBuilder();
188         builder.addGridded(selectAll);
189         builder.addGridded(deselectAll);
190         builder.addGridded(deselectAllDuplicates);
191         builder.addRelatedGap();
192         builder.addGridded(delete);
193         builder.addRelatedGap();
194         builder.addGridded(autoGenerate);
195         builder.addGridded(generate);
196         builder.getPanel().setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
197         centerPan.add(builder.getPanel(), BorderLayout.WEST);
198
199         ok.setEnabled(false);
200         generate.setEnabled(false);
201         ok.addActionListener(new OkListener());
202         cancel.addActionListener(new CancelListener());
203         generate.addActionListener(new GenerateListener());
204         stop.addActionListener(new StopListener());
205         selectAll.addActionListener(new SelectionButton(true));
206         deselectAll.addActionListener(new SelectionButton(false));
207         deselectAllDuplicates.addActionListener(new DeselectDuplicatesButtonListener());
208         deselectAllDuplicates.setEnabled(false);
209         delete.addActionListener(deleteListener);
210         help.addActionListener(new HelpAction(frame.helpDiag, GUIGlobals.importInspectionHelp));
211         getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);
212
213         // Remember and default to last size:
214         setSize(new Dimension(Globals.prefs.getInt("importInspectionDialogWidth"),
215                 Globals.prefs.getInt("importInspectionDialogHeight")));
216         addWindowListener(new WindowAdapter() {
217
218             public void windowOpened(WindowEvent e) {
219                 contentPane.setDividerLocation(0.5f);
220             }
221
222             public void windowClosed(WindowEvent e) {
223                 Globals.prefs.putInt("importInspectionDialogWidth", getSize().width);
224                 Globals.prefs.putInt("importInspectionDialogHeight", getSize().height);
225             }
226         });
227
228     }
229
230     public void setProgress(int current, int max) {
231         progressBar.setIndeterminate(false);
232         progressBar.setMinimum(0);
233         progressBar.setMaximum(max);
234         progressBar.setValue(current);
235     }
236
237     /**
238      * Wrapper for addEntries(List) that takes a single entry.
239      *
240      * @param entry The entry to add.
241      */
242     public void addEntry(BibtexEntry entry) {
243         List list = new ArrayList();
244         list.add(entry);
245         addEntries(list);
246     }
247
248     /**
249      * Add a List of entries to the table view. The table will update to show the
250      * added entries. Synchronizes on this.entries to avoid conflict with the delete button
251      * which removes entries.
252      *
253      * @param entries
254      */
255     public void addEntries(Collection entries) {
256
257         for (Iterator i = entries.iterator(); i.hasNext();) {
258             BibtexEntry entry = (BibtexEntry) i.next();
259             // We exploit the entry's search status for indicating "Keep" status:
260             entry.setSearchHit(defaultSelected);
261             // We exploit the entry's group status for indicating duplicate status.
262             // Checking duplicates means both checking against the background database (if
263             // applicable) and against entries already in the table.
264             if (((panel != null) && (Util.containsDuplicate(panel.database(), entry) != null))
265                 || (internalDuplicate(this.entries, entry) != null)) {
266                 entry.setGroupHit(true);
267                 deselectAllDuplicates.setEnabled(true);
268             }
269             this.entries.getReadWriteLock().writeLock().lock();
270             this.entries.add(entry);
271             this.entries.getReadWriteLock().writeLock().unlock();
272         }
273     }
274
275     /**
276      * Checks if there are duplicates to the given entry in the Collection. Does not
277      * report the entry as duplicate of itself if it is in the Collection.
278      * @param entries A Collection of BibtexEntry instances.
279      * @param entry The entry to search for duplicates of.
280      * @return A possible duplicate, if any, or null if none were found.
281      */
282     protected BibtexEntry internalDuplicate(Collection entries, BibtexEntry entry) {
283         for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
284             BibtexEntry othEntry = (BibtexEntry) iterator.next();
285             if (othEntry == entry)
286                 continue; // Don't compare the entry to itself
287             if (Util.isDuplicate(entry, othEntry, Globals.duplicateThreshold))
288                 return othEntry;
289         }
290         return null;
291     }
292
293     /**
294      * Removes all selected entries from the table. Synchronizes on this.entries to prevent
295      * conflict with addition of new entries.
296      */
297     public void removeSelectedEntries() {
298         int row = glTable.getSelectedRow();
299         List toRemove = new ArrayList();
300         toRemove.addAll(selectionModel.getSelected());
301         entries.getReadWriteLock().writeLock().lock();
302         for (Iterator i=toRemove.iterator(); i.hasNext();) {
303             entries.remove(i.next());
304         }
305         entries.getReadWriteLock().writeLock().unlock();
306         glTable.clearSelection();
307         if ((row >= 0) && (entries.size() > 0)) {
308             row = Math.min(entries.size()-1, row);
309             glTable.addRowSelectionInterval(row, row);
310         }
311     }
312
313     /**
314      * When this method is called, the dialog will visually change to indicate
315      * that all entries are in place.
316      */
317     public void entryListComplete() {
318         progressBar.setIndeterminate(false);
319         progressBar.setVisible(false);
320         ok.setEnabled(true);
321         if (!generatedKeys)
322             generate.setEnabled(true);
323         stop.setEnabled(false);
324     }
325
326
327     /**
328      * This method returns a List containing all entries that are selected
329      * (checkbox checked).
330      *
331      * @return a List containing the selected entries.
332      */
333     public List getSelectedEntries() {
334         List selected = new ArrayList();
335         for (Iterator i=entries.iterator(); i.hasNext();) {
336             BibtexEntry entry = (BibtexEntry)i.next();
337             if (entry.isSearchHit())
338                 selected.add(entry);
339         }
340         /*for (int i = 0; i < table.getRowCount(); i++) {
341             Boolean sel = (Boolean) table.getValueAt(i, 0);
342             if (sel.booleanValue()) {
343                 selected.add(entries.get(i));
344             }
345         }*/
346         return selected;
347     }
348
349     /**
350      * Generate key for the selected entry only.
351      */
352     public void generateKeySelectedEntry() {
353         if (selectionModel.getSelected().size() != 1)
354                 return;
355         BibtexEntry entry = (BibtexEntry) selectionModel.getSelected().get(0);
356         entries.getReadWriteLock().writeLock().lock();
357         BibtexDatabase database = null;
358         // Relate to the existing database, if any:
359         if (panel != null)
360             database = panel.database();
361         // ... or create a temporary one:
362         else
363             database = new BibtexDatabase();
364         try {
365             entry.setId(Util.createNeutralId());
366             // Add the entry to the database we are working with:
367             database.insertEntry(entry);
368         } catch (KeyCollisionException ex) {
369             ex.printStackTrace();
370         }
371         // Generate a unique key:
372         LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, entry);
373         // Remove the entry from the database again, since we only added it in order to
374         // make sure the key was unique:
375         database.removeEntry(entry.getId());
376         
377         entries.getReadWriteLock().writeLock().lock();
378         glTable.repaint();
379     }
380
381     /**
382      * Generate keys for all entries. All keys will be unique with respect to one another,
383      * and, if they are destined for an existing database, with respect to existing keys in
384      * the database.
385      */
386     public void generateKeys(boolean addColumn) {
387         entries.getReadWriteLock().writeLock().lock();
388         BibtexDatabase database = null;
389         // Relate to the existing database, if any:
390         if (panel != null)
391             database = panel.database();
392         // ... or create a temporary one:
393         else
394             database = new BibtexDatabase();
395         List keys = new ArrayList(entries.size());
396         // Iterate over the entries, add them to the database we are working with,
397         // and generate unique keys:
398         for (Iterator i = entries.iterator(); i.hasNext();) {
399             BibtexEntry entry = (BibtexEntry) i.next();
400             //if (newDatabase) {
401             try {
402                 entry.setId(Util.createNeutralId());
403                 database.insertEntry(entry);
404             } catch (KeyCollisionException ex) {
405                 ex.printStackTrace();
406             }
407             //}
408             LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, entry);
409             // Add the generated key to our list:
410             keys.add(entry.getCiteKey());
411         }
412         // Remove the entries from the database again, since they are not supposed to
413         // added yet. They only needed to be in it while we generated the keys, to keep
414         // control over key uniqueness.
415         for (Iterator i = entries.iterator(); i.hasNext();) {
416             BibtexEntry entry = (BibtexEntry) i.next();
417             database.removeEntry(entry.getId());
418         }
419         entries.getReadWriteLock().writeLock().lock();
420         glTable.repaint();
421     }
422
423
424     public void insertNodes(JMenu menu, GroupTreeNode node, boolean add) {
425         final AbstractAction action = getAction(node, add);
426
427         if (node.getChildCount() == 0) {
428             menu.add(action);
429             if (action.isEnabled())
430                 menu.setEnabled(true);
431             return;
432         }
433
434         JMenu submenu = null;
435         if (node.getGroup() instanceof AllEntriesGroup) {
436             for (int i = 0; i < node.getChildCount(); ++i) {
437                 insertNodes(menu, (GroupTreeNode) node.getChildAt(i), add);
438             }
439         } else {
440             submenu = new JMenu("[" + node.getGroup().getName() + "]");
441             // setEnabled(true) is done above/below if at least one menu
442             // entry (item or submenu) is enabled
443             submenu.setEnabled(action.isEnabled());
444             submenu.add(action);
445             submenu.add(new JPopupMenu.Separator());
446             for (int i = 0; i < node.getChildCount(); ++i)
447                 insertNodes(submenu, (GroupTreeNode) node.getChildAt(i), add);
448             menu.add(submenu);
449             if (submenu.isEnabled())
450                 menu.setEnabled(true);
451         }
452     }
453
454     private AbstractAction getAction(GroupTreeNode node, boolean add) {
455         AbstractAction action = add ? (AbstractAction) new AddToGroupAction(node)
456                 : (AbstractAction) new RemoveFromGroupAction(node);
457         AbstractGroup group = node.getGroup();
458         action.setEnabled(/*add ? */group.supportsAdd());// && !group.containsAll(selection)
459         //        : group.supportsRemove() && group.containsAny(selection));
460         return action;
461     }
462
463     /**
464      * Stores the information about the selected entries being scheduled for addition
465      * to this group. The entries are *not* added to the group at this time.
466      * <p/>
467      * Synchronizes on this.entries to prevent
468      * conflict with threads that modify the entry list.
469      */
470     class AddToGroupAction extends AbstractAction {
471         private GroupTreeNode node;
472
473         public AddToGroupAction(GroupTreeNode node) {
474             super(node.getGroup().getName());
475             this.node = node;
476         }
477
478         public void actionPerformed(ActionEvent event) {
479
480             selectionModel.getSelected().getReadWriteLock().writeLock().lock();
481             for (Iterator i=selectionModel.getSelected().iterator(); i.hasNext();) {
482                 BibtexEntry entry = (BibtexEntry)i.next();
483                 // We store the groups this entry should be added to in a Set in the Map:
484                 Set groups = (Set) groupAdditions.get(entry);
485                 if (groups == null) {
486                     // No previous definitions, so we create the Set now:
487                     groups = new HashSet();
488                     groupAdditions.put(entry, groups);
489                 }
490                 // Add the group:
491                 groups.add(node);
492             }
493             selectionModel.getSelected().getReadWriteLock().writeLock().unlock();
494         }
495     }
496
497     class RemoveFromGroupAction extends AbstractAction {
498         private GroupTreeNode node;
499
500         public RemoveFromGroupAction(GroupTreeNode node) {
501             this.node = node;
502         }
503
504         public void actionPerformed(ActionEvent event) {
505         }
506     }
507
508     public void addCallBack(CallBack cb) {
509         callBacks.add(cb);
510     }
511
512     class OkListener implements ActionListener {
513         public void actionPerformed(ActionEvent event) {
514
515             // First check if we are supposed to warn about duplicates. If so, see if there
516             // are unresolved duplicates, and warn if yes.
517             if (Globals.prefs.getBoolean("warnAboutDuplicatesInInspection")) {
518                 for (Iterator i=entries.iterator(); i.hasNext();) {
519
520                     BibtexEntry entry = (BibtexEntry)i.next();
521                     // Only check entries that are to be imported. Keep status is indicated
522                     // through the search hit status of the entry:
523                     if (!entry.isSearchHit())
524                         continue;
525
526                     // Check if the entry is a suspected, unresolved, duplicate. This status
527                     // is indicated by the entry's group hit status:
528                     if (entry.isGroupHit()) {
529                         CheckBoxMessage cbm = new CheckBoxMessage(
530                                 Globals.lang("There are possible duplicates (marked with a 'D' icon) that haven't been resolved. Continue?"),
531                                 Globals.lang("Disable this confirmation dialog"), false);
532                         int answer = JOptionPane.showConfirmDialog(ImportInspectionDialog.this, cbm, Globals.lang("Duplicates found"),
533                                     JOptionPane.YES_NO_OPTION);
534                         if (cbm.isSelected())
535                             Globals.prefs.putBoolean("warnAboutDuplicatesInInspection", false);
536                         if (answer == JOptionPane.NO_OPTION)
537                             return;
538                         break;
539                     }
540                 }
541             }
542
543             // The compund undo action used to contain all changes made by this dialog.
544             NamedCompound ce = new NamedCompound(undoName);
545
546             // See if we should remove any old entries for duplicate resolving:
547             if (entriesToDelete.size() > 0) {
548                 for (Iterator i=entriesToDelete.iterator(); i.hasNext();) {
549                     BibtexEntry entry = (BibtexEntry)i.next();
550                     ce.addEdit(new UndoableRemoveEntry(panel.database(), entry, panel));
551                     panel.database().removeEntry(entry.getId());
552                 }
553             }
554             /*panel.undoManager.addEdit(undo);
555             panel.refreshTable();
556             panel.markBaseChanged();*/
557
558
559             // If "Generate keys" is checked, generate keys unless it's already been done:
560             if (autoGenerate.isSelected() && !generatedKeys) {
561                 generateKeys(false);
562             }
563             // Remember the choice until next time:
564             Globals.prefs.putBoolean("generateKeysAfterInspection", autoGenerate.isSelected());
565
566             final List selected = getSelectedEntries();
567
568             if (selected.size() > 0) {
569
570                 if (newDatabase) {
571                     // Create a new BasePanel for the entries:
572                     BibtexDatabase base = new BibtexDatabase();
573                     panel = new BasePanel(frame, base, null, new HashMap(), Globals.prefs.get("defaultEncoding"));
574                 }
575
576                 boolean groupingCanceled = false;
577
578                 // Set owner/timestamp if options are enabled:
579                 Util.setAutomaticFields(selected, Globals.prefs.getBoolean("overwriteOwner"),
580                     Globals.prefs.getBoolean("overwriteTimeStamp"));
581
582
583                 for (Iterator i = selected.iterator(); i.hasNext();) {
584                     BibtexEntry entry = (BibtexEntry) i.next();
585                     //entry.clone();
586
587                     // Remove settings to group/search hit status:
588                     entry.setSearchHit(false);
589                     entry.setGroupHit(false);
590
591                     // If this entry should be added to any groups, do it now:
592                     Set groups = (Set) groupAdditions.get(entry);
593                     if (!groupingCanceled && (groups != null)) {
594                         if (entry.getField(BibtexFields.KEY_FIELD) == null) {
595                             // The entry has no key, so it can't be added to the group.
596                             // The best course of ation is probably to ask the user if a key should be generated
597                             // immediately.
598                            int answer = JOptionPane.showConfirmDialog(ImportInspectionDialog.this,
599                                    Globals.lang("Cannot add entries to group without generating keys. Generate keys now?"),
600                                     Globals.lang("Add to group"), JOptionPane.YES_NO_OPTION);
601                             if (answer == JOptionPane.YES_OPTION) {
602                                 generateKeys(false);
603                             } else
604                                 groupingCanceled = true;
605                         }
606
607                         // If the key was list, or has been list now, go ahead:
608                         if (entry.getField(BibtexFields.KEY_FIELD) != null) {
609                             for (Iterator i2 = groups.iterator(); i2.hasNext();) {
610                                 GroupTreeNode node = (GroupTreeNode) i2.next();
611                                 if (node.getGroup().supportsAdd()) {
612                                     // Add the entry:
613                                     AbstractUndoableEdit undo = node.getGroup().add(new BibtexEntry[]{entry});
614                                     if (undo instanceof UndoableChangeAssignment)
615                                         ((UndoableChangeAssignment) undo).setEditedNode(node);
616                                     ce.addEdit(undo);
617
618                                 } else {
619                                     // Shouldn't happen...
620                                 }
621                             }
622                         }
623                     }
624
625                     try {
626                         entry.setId(Util.createNeutralId());
627                         panel.database().insertEntry(entry);
628                         // Let the autocompleters, if any, harvest words from the entry: 
629                         Util.updateCompletersForEntry(panel.getAutoCompleters(), entry);
630                         ce.addEdit(new UndoableInsertEntry(panel.database(), entry, panel));
631                     } catch (KeyCollisionException e) {
632                         e.printStackTrace();
633                     }
634                 }
635
636                 ce.end();
637                 panel.undoManager.addEdit(ce);
638             }
639
640             dispose();
641             SwingUtilities.invokeLater(new Thread() {
642                 public void run() {
643                     if (newDatabase) {
644                         frame.addTab(panel, null, true);
645                     }
646                     panel.markBaseChanged();
647                     for (Iterator i = callBacks.iterator(); i.hasNext();) {
648                         ((CallBack) i.next()).done(selected.size());
649                     }
650                 }
651             });
652
653         }
654
655     }
656
657     private void signalStopFetching() {
658         for (Iterator i = callBacks.iterator(); i.hasNext();) {
659             ((CallBack) i.next()).stopFetching();
660         }
661     }
662
663     private void setWidths() {
664         TableColumnModel cm = glTable.getColumnModel();
665         cm.getColumn(0).setPreferredWidth(55);
666         cm.getColumn(0).setMinWidth(55);
667         cm.getColumn(0).setMaxWidth(55);
668         for (int i = 1; i < PAD; i++) {
669             // Lock the width of icon columns.
670             cm.getColumn(i).setPreferredWidth(GUIGlobals.WIDTH_ICON_COL);
671             cm.getColumn(i).setMinWidth(GUIGlobals.WIDTH_ICON_COL);
672             cm.getColumn(i).setMaxWidth(GUIGlobals.WIDTH_ICON_COL);
673         }
674
675         for (int i = 0; i < fields.length; i++) {
676             int width = BibtexFields.getFieldLength( fields[i]) ;
677             glTable.getColumnModel().getColumn(i + PAD).setPreferredWidth(width);
678         }
679     }
680
681
682
683     class StopListener implements ActionListener {
684         public void actionPerformed(ActionEvent event) {
685             signalStopFetching();
686             entryListComplete();
687         }
688     }
689
690     class CancelListener implements ActionListener {
691         public void actionPerformed(ActionEvent event) {
692             signalStopFetching();
693             dispose();
694             for (Iterator i = callBacks.iterator(); i.hasNext();) {
695                 ((CallBack) i.next()).cancelled();
696             }
697         }
698     }
699
700     class GenerateListener implements ActionListener {
701         public void actionPerformed(ActionEvent event) {
702             generate.setEnabled(false);
703             generatedKeys = true; // To prevent the button from getting enabled again.
704             generateKeys(true); // Generate the keys.
705         }
706     }
707
708     class DeleteListener extends AbstractAction implements ActionListener {
709         public DeleteListener() {
710             super(Globals.lang("Delete"), GUIGlobals.getImage("delete"));
711         }
712
713         public void actionPerformed(ActionEvent event) {
714             removeSelectedEntries();
715         }
716     }
717
718     class MyTable extends JTable {
719         public MyTable(TableModel model) {
720             super(model);
721             //setDefaultRenderer(Boolean.class, );
722         }
723
724         public boolean isCellEditable(int row, int col) {
725             return col == 0;
726         }
727     }
728
729     class MyTableModel extends DefaultTableModel {
730
731
732         public Class getColumnClass(int i) {
733             if (i == 0)
734                 return Boolean.class;
735             else
736                 return String.class;
737         }
738
739     }
740
741     class SelectionButton implements ActionListener {
742         private Boolean enable;
743
744         public SelectionButton(boolean enable) {
745             this.enable = Boolean.valueOf(enable);
746         }
747
748         public void actionPerformed(ActionEvent event) {
749             for (int i = 0; i < glTable.getRowCount(); i++) {
750                 glTable.setValueAt(enable, i, 0);
751             }
752             glTable.repaint();
753         }
754     }
755     
756     class DeselectDuplicatesButtonListener implements ActionListener {
757         public void actionPerformed(ActionEvent event) {
758             for (int i = 0; i < glTable.getRowCount(); i++) {
759                 if(glTable.getValueAt(i, DUPL_COL) != null){
760                         glTable.setValueAt(Boolean.valueOf(false), i, 0);
761                 }
762             }
763             glTable.repaint();
764         }
765     }
766     
767     class EntrySelectionListener implements ListEventListener {
768
769         public void listChanged(ListEvent listEvent) {
770             if (listEvent.getSourceList().size() == 1) {
771                 preview.setEntry((BibtexEntry) listEvent.getSourceList().get(0));
772                 contentPane.setDividerLocation(0.5f);
773                 SwingUtilities.invokeLater(new Runnable() {
774                     public void run() {
775                         preview.scrollRectToVisible(toRect);
776                     }
777                 });
778             }
779         }
780     }
781
782
783     /**
784      * This class handles clicks on the table that should trigger specific
785      * events, like opening the popup menu.
786      */
787     class TableClickListener implements MouseListener {
788
789         public boolean isIconColumn(int col) {
790             return (col == FILE_COL) || (col == PDF_COL) || (col == PS_COL)
791                     || (col == URL_COL);
792         }
793
794         public void mouseClicked(MouseEvent e) {
795             final int col = glTable.columnAtPoint(e.getPoint()),
796               row = glTable.rowAtPoint(e.getPoint());
797             if (isIconColumn(col)) {
798                 BibtexEntry entry = (BibtexEntry)sortedList.get(row);
799
800                 switch (col) {
801                     case FILE_COL:
802                         Object o = entry.getField(GUIGlobals.FILE_FIELD);
803                         if (o != null) {
804                             FileListTableModel tableModel = new FileListTableModel();
805                             tableModel.setContent((String)o);
806                             if (tableModel.getRowCount() == 0)
807                                 return;
808                             FileListEntry fl = tableModel.getEntry(0);
809                             (new ExternalFileMenuItem(frame, entry, "", fl.getLink(), null, panel.metaData(), fl.getType())).
810                                     actionPerformed(null);
811                         }
812                         break;
813                     case URL_COL:
814                         openExternalLink("url", e);
815                         break;
816                     case PDF_COL:
817                         openExternalLink("pdf", e);
818                         break;
819                     case PS_COL:
820                         openExternalLink("ps", e);
821                         break;
822                 }
823             }
824         }
825
826         public void mouseEntered(MouseEvent e) {
827
828         }
829
830         public void mouseExited(MouseEvent e) {
831
832         }
833
834         /**
835          * Show right-click menu. If the click happened in an icon column that presents its own popup menu,
836          * show that. Otherwise, show the ordinary popup menu.
837          * @param e The mouse event that triggered the popup.
838          */
839         public void showPopup(MouseEvent e) {
840             final int col = glTable.columnAtPoint(e.getPoint());
841             switch (col) {
842                 case FILE_COL:
843                     showFileFieldMenu(e);
844                     break;
845                 default:
846                     showOrdinaryRightClickMenu(e);
847                     break;
848             }
849
850         }
851
852         public void showOrdinaryRightClickMenu(MouseEvent e) {
853             popup.show(glTable, e.getX(), e.getY());
854         }
855
856         /**
857          * Show the popup menu for the FILE field.
858          * @param e The mouse event that triggered the popup.
859          */
860         public void showFileFieldMenu(MouseEvent e) {
861             final int row = glTable.rowAtPoint(e.getPoint());
862             BibtexEntry entry = (BibtexEntry)sortedList.get(row);
863             JPopupMenu menu = new JPopupMenu();
864             int count = 0;
865             Object o = entry.getField(GUIGlobals.FILE_FIELD);
866             FileListTableModel fileList = new FileListTableModel();
867             fileList.setContent((String)o);
868             // If there are one or more links, open the first one:
869             for (int i=0; i<fileList.getRowCount(); i++) {
870                 FileListEntry flEntry = fileList.getEntry(i);
871                 String description = flEntry.getDescription();
872                 if ((description == null) || (description.trim().length() == 0))
873                     description = flEntry.getLink();
874                 menu.add(new ExternalFileMenuItem(panel.frame(), entry, description,
875                         flEntry.getLink(), flEntry.getType().getIcon(), panel.metaData(),
876                         flEntry.getType()));
877                 count++;
878             }
879             if (count == 0) {
880                 showOrdinaryRightClickMenu(e);
881             }
882             else
883                 menu.show(glTable, e.getX(), e.getY());
884         }
885
886         /**
887          * Open old-style external links after user clicks icon.
888          * @param fieldName The name of the BibTeX field this icon is used for.
889          * @param e The MouseEvent that triggered this operation.
890          */
891         public void openExternalLink(String fieldName, MouseEvent e) {
892             final int row = glTable.rowAtPoint(e.getPoint());
893             BibtexEntry entry = (BibtexEntry)sortedList.get(row);
894
895             Object link = entry.getField(fieldName);
896             try {
897                 if (link != null)
898                     Util.openExternalViewer(panel.metaData(), (String) link, fieldName);
899             } catch (IOException ex) {
900                 ex.printStackTrace();
901             }
902         }
903
904
905         public void mouseReleased(MouseEvent e) {
906             // Check if the user has right-clicked. If so, open the right-click menu.
907             if (e.isPopupTrigger()) {
908                 showPopup(e);
909                 return;
910             }
911         }
912
913         public void mousePressed(MouseEvent e) {
914             // Check if the user has right-clicked. If so, open the right-click menu.
915             if (e.isPopupTrigger()) {
916                 showPopup(e);
917                 return;
918             }
919
920             // Check if any other action should be taken:
921             final int col = glTable.columnAtPoint(e.getPoint()),
922               row = glTable.rowAtPoint(e.getPoint());
923             // Is this the duplicate icon column, and is there an icon?
924             if ((col == DUPL_COL) && (glTable.getValueAt(row, col) != null)) {
925                 BibtexEntry first = (BibtexEntry)sortedList.get(row);
926                 BibtexEntry other = Util.containsDuplicate(panel.database(), first);
927                 if (other != null) {
928                     // This will be true if the duplicate is in the existing
929                     // database.
930                     DuplicateResolverDialog diag = new DuplicateResolverDialog
931                             (ImportInspectionDialog.this, other, first, DuplicateResolverDialog.INSPECTION);
932                     Util.placeDialog(diag, ImportInspectionDialog.this);
933                     diag.setVisible(true);
934                     ImportInspectionDialog.this.toFront();
935                     if (diag.getSelected() == DuplicateResolverDialog.KEEP_UPPER) {
936                         // Remove old entry. Or... add it to a list of entries to be deleted. We only delete
937                         // it after Ok is clicked.
938                         entriesToDelete.add(other);
939                         // Clear duplicate icon, which is controlled by the group hit
940                         // field of the entry:
941                         entries.getReadWriteLock().writeLock().lock();
942                         first.setGroupHit(false);
943                         entries.getReadWriteLock().writeLock().unlock();
944
945                     } else if (diag.getSelected() == DuplicateResolverDialog.KEEP_LOWER) {
946                         // Remove the entry from the import inspection dialog.
947                         entries.getReadWriteLock().writeLock().lock();
948                         entries.remove(first);
949                         entries.getReadWriteLock().writeLock().unlock();
950                     } else if (diag.getSelected() == DuplicateResolverDialog.KEEP_BOTH) {
951                         // Do nothing.
952                         entries.getReadWriteLock().writeLock().lock();
953                         first.setGroupHit(false);
954                         entries.getReadWriteLock().writeLock().unlock();
955                     }
956                 }
957                 // Check if the duplicate is of another entry in the import:
958                 other = internalDuplicate(entries, first);
959                 if (other != null) {
960                     int answer = DuplicateResolverDialog.resolveDuplicate
961                             (ImportInspectionDialog.this, first, other);
962                     if (answer == DuplicateResolverDialog.KEEP_UPPER) {
963                         entries.remove(other);
964                         first.setGroupHit(false);
965                     } else if (answer == DuplicateResolverDialog.KEEP_LOWER) {
966                         entries.remove(first);
967                     } else if (answer == DuplicateResolverDialog.KEEP_BOTH) {
968                         first.setGroupHit(false);
969                     }
970                 }
971             }
972         }
973     }
974
975     class AttachUrl extends JMenuItem implements ActionListener {
976         public AttachUrl() {
977             super(Globals.lang("Attach URL"));
978             addActionListener(this);
979         }
980
981         public void actionPerformed(ActionEvent event) {
982             if (selectionModel.getSelected().size() != 1)
983                 return;
984             BibtexEntry entry = (BibtexEntry) selectionModel.getSelected().get(0);
985             String result = JOptionPane.showInputDialog(ths, Globals.lang("Enter URL"), entry.getField("url"));
986             entries.getReadWriteLock().writeLock().lock();
987             if (result != null) {
988                 if (result.equals("")) {
989                     entry.clearField("url");
990                 } else {
991                     entry.setField("url", result);
992                 }
993             }
994             entries.getReadWriteLock().writeLock().unlock();
995             glTable.repaint();
996         }
997     }
998
999     class DownloadFile extends JMenuItem implements ActionListener,
1000         DownloadExternalFile.DownloadCallback {
1001
1002         BibtexEntry entry = null;
1003
1004         public DownloadFile() {
1005             super(Globals.lang("Download file"));
1006             addActionListener(this);
1007         }
1008
1009         public void actionPerformed(ActionEvent actionEvent) {
1010             if (selectionModel.getSelected().size() != 1)
1011                 return;
1012             entry = (BibtexEntry) selectionModel.getSelected().get(0);
1013             String bibtexKey = entry.getCiteKey();
1014             if (bibtexKey == null) {
1015                 int answer = JOptionPane.showConfirmDialog(frame,
1016                         Globals.lang("This entry has no BibTeX key. Generate key now?"),
1017                         Globals.lang("Download file"), JOptionPane.OK_CANCEL_OPTION,
1018                         JOptionPane.QUESTION_MESSAGE);
1019                 if (answer == JOptionPane.OK_OPTION) {
1020                     generateKeySelectedEntry();
1021                     bibtexKey = entry.getCiteKey();
1022                 }
1023             }
1024             DownloadExternalFile def = new DownloadExternalFile(frame, metaData, bibtexKey);
1025             try {
1026                 def.download(this);
1027             } catch (IOException ex) {
1028                 ex.printStackTrace();
1029             }
1030         }
1031
1032         public void downloadComplete(FileListEntry file) {
1033             ImportInspectionDialog.this.toFront(); // Hack
1034             FileListTableModel model = new FileListTableModel();
1035             String oldVal = (String)entry.getField(GUIGlobals.FILE_FIELD);
1036             if (oldVal != null)
1037                 model.setContent(oldVal);
1038             model.addEntry(model.getRowCount(), file);
1039             entries.getReadWriteLock().writeLock().lock();
1040             entry.setField(GUIGlobals.FILE_FIELD, model.getStringRepresentation());
1041             entries.getReadWriteLock().writeLock().unlock();
1042             glTable.repaint();
1043         }
1044     }
1045
1046     class AutoSetLinks extends JMenuItem implements ActionListener {
1047
1048         public AutoSetLinks() {
1049             super(Globals.lang("Autoset external links"));
1050             addActionListener(this);
1051         }
1052
1053         public void actionPerformed(ActionEvent actionEvent) {
1054             if (selectionModel.getSelected().size() != 1)
1055                 return;
1056             final BibtexEntry entry = (BibtexEntry) selectionModel.getSelected().get(0);
1057             String bibtexKey = entry.getCiteKey();
1058             if (bibtexKey == null) {
1059                 int answer = JOptionPane.showConfirmDialog(frame,
1060                         Globals.lang("This entry has no BibTeX key. Generate key now?"),
1061                         Globals.lang("Download file"), JOptionPane.OK_CANCEL_OPTION,
1062                         JOptionPane.QUESTION_MESSAGE);
1063                 if (answer == JOptionPane.OK_OPTION) {
1064                     generateKeySelectedEntry();
1065                     bibtexKey = entry.getCiteKey();
1066                 } else return; // Can't go on without the bibtex key.
1067             }
1068             final FileListTableModel model = new FileListTableModel();
1069             String oldVal = (String)entry.getField(GUIGlobals.FILE_FIELD);
1070             if (oldVal != null)
1071                 model.setContent(oldVal);
1072             // We have a static utility method for searching for all relevant links:
1073             JDialog diag = new JDialog(ImportInspectionDialog.this, true);
1074             FileListEditor.autoSetLinks(entry, model, metaData, new ActionListener() {
1075                 public void actionPerformed(ActionEvent e) {
1076                     if (e.getID() > 0) {
1077                         entries.getReadWriteLock().writeLock().lock();
1078                         entry.setField(GUIGlobals.FILE_FIELD, model.getStringRepresentation());
1079                         entries.getReadWriteLock().writeLock().unlock();
1080                         glTable.repaint();
1081                     }
1082                 }
1083             }, diag);
1084
1085         }
1086     }
1087
1088     class LinkLocalFile extends JMenuItem implements ActionListener,
1089         DownloadExternalFile.DownloadCallback {
1090
1091         BibtexEntry entry = null;
1092
1093         public LinkLocalFile() {
1094             super(Globals.lang("Link local file"));
1095             addActionListener(this);
1096         }
1097
1098         public void actionPerformed(ActionEvent actionEvent) {
1099             if (selectionModel.getSelected().size() != 1)
1100                 return;
1101             entry = (BibtexEntry) selectionModel.getSelected().get(0);
1102             FileListEntry flEntry = new FileListEntry("", "", null);
1103             FileListEntryEditor editor = new FileListEntryEditor(frame, flEntry, false, metaData);
1104             editor.setVisible(true);                                                 
1105             if (editor.okPressed()) {
1106                 FileListTableModel model = new FileListTableModel();
1107                 String oldVal = (String)entry.getField(GUIGlobals.FILE_FIELD);
1108                 if (oldVal != null)
1109                     model.setContent(oldVal);
1110                 model.addEntry(model.getRowCount(), flEntry);
1111                 entries.getReadWriteLock().writeLock().lock();
1112                 entry.setField(GUIGlobals.FILE_FIELD, model.getStringRepresentation());
1113                 entries.getReadWriteLock().writeLock().unlock();
1114                 glTable.repaint();
1115             }
1116         }
1117
1118         public void downloadComplete(FileListEntry file) {
1119             ImportInspectionDialog.this.toFront(); // Hack
1120             FileListTableModel model = new FileListTableModel();
1121             String oldVal = (String)entry.getField(GUIGlobals.FILE_FIELD);
1122             if (oldVal != null)
1123                 model.setContent(oldVal);
1124             model.addEntry(model.getRowCount(), file);
1125             entries.getReadWriteLock().writeLock().lock();
1126             entry.setField(GUIGlobals.FILE_FIELD, model.getStringRepresentation());
1127             entries.getReadWriteLock().writeLock().unlock();
1128             glTable.repaint();
1129         }
1130     }
1131
1132     class AttachFile extends JMenuItem implements ActionListener {
1133         String fileType;
1134
1135         public AttachFile(String fileType) {
1136             super(Globals.lang("Attach %0 file", new String[]{fileType.toUpperCase()}));
1137             this.fileType = fileType;
1138             addActionListener(this);
1139         }
1140
1141         public void actionPerformed(ActionEvent event) {
1142
1143             if (selectionModel.getSelected().size() != 1)
1144                 return;
1145             BibtexEntry entry = (BibtexEntry) selectionModel.getSelected().get(0);
1146             // Call up a dialog box that provides Browse, Download and auto buttons:
1147             AttachFileDialog diag = new AttachFileDialog(ths, metaData, entry, fileType);
1148             Util.placeDialog(diag, ths);
1149             diag.setVisible(true);
1150             // After the dialog has closed, if it wasn't cancelled, list the field:
1151             if (!diag.cancelled()) {
1152                 entries.getReadWriteLock().writeLock().lock();
1153                 entry.setField(fileType, diag.getValue());
1154                 entries.getReadWriteLock().writeLock().unlock();
1155                 glTable.repaint();
1156             }
1157
1158         }
1159     }
1160
1161     public static interface CallBack {
1162         // This method is called by the dialog when the user has selected the
1163         // wanted entries, and clicked Ok. The callback object can update status
1164         // line etc.
1165         public void done(int entriesImported);
1166
1167         // This method is called by the dialog when the user has cancelled the import.
1168         public void cancelled();
1169
1170         // This method is called by the dialog when the user has cancelled or
1171         // signalled a stop. It is expected that any long-running fetch operations
1172         // will stop after this method is called.
1173         public void stopFetching();
1174     }
1175
1176
1177     private void setupComparatorChooser() {
1178         // First column:
1179         java.util.List comparators = comparatorChooser.getComparatorsForColumn(0);
1180         comparators.clear();
1181
1182         comparators = comparatorChooser.getComparatorsForColumn(1);
1183         comparators.clear();
1184
1185         // Icon columns:
1186         for (int i = 2; i < PAD; i++) {
1187             comparators = comparatorChooser.getComparatorsForColumn(i);
1188             comparators.clear();
1189             if (i == FILE_COL)
1190                 comparators.add(new IconComparator(new String[] {GUIGlobals.FILE_FIELD}));
1191             else if (i == PDF_COL)
1192                 comparators.add(new IconComparator(new String[] {"pdf"}));
1193             else if (i == PS_COL)
1194                 comparators.add(new IconComparator(new String[] {"ps"}));
1195             else if (i == URL_COL)
1196                 comparators.add(new IconComparator(new String[] {"url"}));
1197
1198         }
1199         // Remaining columns:
1200         for (int i = PAD; i < PAD+fields.length; i++) {
1201             comparators = comparatorChooser.getComparatorsForColumn(i);
1202             comparators.clear();
1203             comparators.add(new FieldComparator(fields[i-PAD]));
1204         }
1205
1206         // Set initial sort columns:
1207
1208         /*// Default sort order:
1209         String[] sortFields = new String[] {Globals.prefs.get("priSort"), Globals.prefs.get("secSort"),
1210             Globals.prefs.get("terSort")};
1211         boolean[] sortDirections = new boolean[] {Globals.prefs.getBoolean("priDescending"),
1212             Globals.prefs.getBoolean("secDescending"), Globals.prefs.getBoolean("terDescending")}; // descending
1213         */
1214         sortedList.getReadWriteLock().writeLock().lock();
1215         comparatorChooser.appendComparator(PAD, 0, false);
1216         sortedList.getReadWriteLock().writeLock().unlock();
1217
1218     }
1219
1220     class EntryTable extends JTable {
1221         GeneralRenderer renderer = new GeneralRenderer(Color.white);
1222         public EntryTable(TableModel model) {
1223             super(model);
1224         }
1225         public TableCellRenderer getCellRenderer(int row, int column) {
1226             return column == 0 ? getDefaultRenderer(Boolean.class) : renderer;
1227         }
1228
1229         /*public TableCellEditor getCellEditor() {
1230             return getDefaultEditor(Boolean.class);
1231         } */
1232
1233         public Class getColumnClass(int col) {
1234             if (col == 0)
1235                 return Boolean.class;
1236             else if (col < PAD)
1237                 return JLabel.class;
1238             else return String.class;
1239         }
1240
1241         public boolean isCellEditable(int row, int column) {
1242             return column == 0;
1243         }
1244
1245         public void setValueAt(Object value, int row, int column) {
1246             // Only column 0, which is controlled by BibtexEntry.searchHit, is editable:
1247             entries.getReadWriteLock().writeLock().lock();
1248             BibtexEntry entry = (BibtexEntry)sortedList.get(row);
1249             entry.setSearchHit(((Boolean)value).booleanValue());
1250             entries.getReadWriteLock().writeLock().unlock();
1251         }
1252     }
1253
1254     class EntryTableFormat implements TableFormat {
1255         public int getColumnCount() {
1256             return PAD+fields.length;
1257         }
1258
1259         public String getColumnName(int i) {
1260             if (i == 0)
1261                 return Globals.lang("Keep");
1262             if (i >= PAD) {
1263                 return Util.nCase(fields[i-PAD]);
1264             }
1265             return "";
1266         }
1267
1268         public Object getColumnValue(Object object, int i) {
1269             BibtexEntry entry = (BibtexEntry)object;
1270             if (i == 0)
1271                 return entry.isSearchHit() ? Boolean.TRUE : Boolean.FALSE;
1272             else if (i < PAD) {
1273                 Object o;
1274                 switch (i) {
1275                     case DUPL_COL: return entry.isGroupHit() ?  duplLabel : null;
1276                     case FILE_COL:
1277                         o = entry.getField(GUIGlobals.FILE_FIELD);
1278                         if (o != null) {
1279                             FileListTableModel model = new FileListTableModel();
1280                             model.setContent((String)o);
1281                             fileLabel.setToolTipText(model.getToolTipHTMLRepresentation());
1282                             return fileLabel;
1283                         } else return null;
1284                     case PDF_COL:
1285                         o = entry.getField("pdf");
1286                         if (o != null) {
1287                             pdfLabel.setToolTipText((String)o);
1288                             return pdfLabel;
1289                         } else return null;
1290
1291                     case PS_COL:
1292                         o = entry.getField("ps");
1293                         if (o != null) {
1294                             psLabel.setToolTipText((String)o);
1295                             return psLabel;
1296                         } else return null;
1297                     case URL_COL:
1298                         o = entry.getField("url");
1299                         if (o != null) {
1300                             urlLabel.setToolTipText((String)o);
1301                             return urlLabel;
1302                         } else return null;
1303                     default: return null;
1304                 }
1305             }
1306             else {
1307                 String field = fields[i-PAD];
1308                 if (field.equals("author") || field.equals("editor")) {
1309                     String contents = (String)entry.getField(field);
1310                     return (contents != null) ?
1311                         AuthorList.fixAuthor_Natbib(contents) : "";
1312                 }
1313                 else
1314                     return entry.getField(field);
1315             }
1316         }
1317
1318     }
1319 }