69e84f84908bf2a1d619f930262af57b42a1af60
[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.groups.GroupTreeNode;
5 import net.sf.jabref.groups.AllEntriesGroup;
6 import net.sf.jabref.groups.AbstractGroup;
7 import net.sf.jabref.groups.UndoableChangeAssignment;
8 import net.sf.jabref.labelPattern.LabelPatternUtil;
9 import net.sf.jabref.undo.NamedCompound;
10 import net.sf.jabref.undo.UndoableInsertEntry;
11 import net.sf.jabref.undo.UndoableRemoveEntry;
12
13 import javax.swing.*;
14 import javax.swing.undo.AbstractUndoableEdit;
15 import javax.swing.event.ListSelectionListener;
16 import javax.swing.event.ListSelectionEvent;
17 import javax.swing.table.*;
18 import java.util.*;
19 import java.util.List;
20 import java.awt.*;
21 import java.awt.event.*;
22
23 import com.jgoodies.forms.builder.ButtonBarBuilder;
24 import com.jgoodies.forms.builder.ButtonStackBuilder;
25 import com.jgoodies.uif_lite.component.UIFSplitPane;
26 import ca.odell.glazedlists.*;
27 import ca.odell.glazedlists.event.ListEventListener;
28 import ca.odell.glazedlists.event.ListEvent;
29 import ca.odell.glazedlists.gui.TableFormat;
30 import ca.odell.glazedlists.swing.TableComparatorChooser;
31 import ca.odell.glazedlists.swing.EventTableModel;
32 import ca.odell.glazedlists.swing.EventSelectionModel;
33
34 /**
35  * Created by IntelliJ IDEA.
36  * User: alver
37  * Date: 20.mar.2005
38  * Time: 22:02:35
39  * To change this template use File | Settings | File Templates.
40  */
41 public class ImportInspectionDialog extends JDialog {
42     private ImportInspectionDialog ths = this;
43     private BasePanel panel;
44     private JabRefFrame frame;
45     private MetaData metaData;
46     private UIFSplitPane contentPane = new UIFSplitPane(UIFSplitPane.VERTICAL_SPLIT);
47     //private MyTableModel tableModel = new MyTableModel();
48     //private JTable table = new MyTable(tableModel);
49     private JTable glTable;
50     private TableComparatorChooser comparatorChooser;
51     private EventSelectionModel selectionModel;
52     private String[] fields;
53     private JProgressBar progressBar = new JProgressBar(JProgressBar.HORIZONTAL);
54     private JButton ok = new JButton(Globals.lang("Ok")),
55         cancel = new JButton(Globals.lang("Cancel")),
56         generate = new JButton(Globals.lang("Generate now"));
57     private EventList entries = new BasicEventList();
58     private SortedList sortedList;
59     private List entriesToDelete = new ArrayList(); // Duplicate resolving may require deletion of old entries.
60     private String undoName;
61     private ArrayList callBacks = new ArrayList();
62     private boolean newDatabase;
63     private JMenu groupsAdd = new JMenu(Globals.lang("Add to group"));
64     private JPopupMenu popup = new JPopupMenu();
65     private JButton selectAll = new JButton(Globals.lang("Select all"));
66     private JButton deselectAll = new JButton(Globals.lang("Deselect all"));
67     private JButton stop = new JButton(Globals.lang("Stop"));
68     private JButton delete = new JButton(Globals.lang("Delete"));
69     private JButton help = new JButton(Globals.lang("Help"));
70     private PreviewPanel preview;
71     private ListSelectionListener previewListener = null;
72     private boolean generatedKeys = false; // Set to true after keys have been generated.
73     private boolean defaultSelected = true;
74     private Rectangle toRect = new Rectangle(0, 0, 1, 1);
75     private Map groupAdditions = new HashMap();
76     private JCheckBox autoGenerate = new JCheckBox(Globals.lang("Generate keys"), Globals.prefs.getBoolean("generateKeysAfterInspection"));
77     private JLabel
78         duplLabel = new JLabel(new ImageIcon(GUIGlobals.duplicateIcon)),
79         pdfLabel = new JLabel(new ImageIcon(GUIGlobals.pdfIcon)),
80         psLabel = new JLabel(new ImageIcon(GUIGlobals.psIcon)),
81         urlLabel = new JLabel(new ImageIcon(GUIGlobals.wwwIcon));
82
83     private final int
84         DUPL_COL = 1,
85         PDF_COL = 2,
86         PS_COL = 3,
87         URL_COL = 4,
88         PAD = 5;
89
90
91     /**
92      * The "defaultSelected" boolean value determines if new entries added are selected for import or not.
93      * This value is true by default.
94      * @param defaultSelected The desired value.
95      */
96     public void setDefaultSelected(boolean defaultSelected) {
97         this.defaultSelected = defaultSelected;
98     }
99
100     /**
101      * Creates a dialog that displays the given list of fields in the table.
102      * The dialog allows another process to add entries dynamically while the dialog
103      * is shown.
104      *
105      * @param frame
106      * @param panel
107      * @param fields
108      */
109     public ImportInspectionDialog(JabRefFrame frame, BasePanel panel, String[] fields,
110                                   String undoName, boolean newDatabase) {
111         this.frame = frame;
112         this.panel = panel;
113         this.metaData = (panel != null) ? panel.metaData() : new MetaData();
114         this.fields = fields;
115         this.undoName = undoName;
116         this.newDatabase = newDatabase;
117         preview = new PreviewPanel(Globals.prefs.get("preview1"), metaData);
118
119         duplLabel.setToolTipText(Globals.lang("Possible duplicate of existing entry. Click to resolve."));
120
121         sortedList = new SortedList(entries);
122         EventTableModel tableModelGl = new EventTableModel(sortedList,
123                 new EntryTableFormat());
124         glTable = new EntryTable(tableModelGl);
125         GeneralRenderer renderer = new GeneralRenderer(Color.white, true);
126         glTable.setDefaultRenderer(JLabel.class, renderer);
127         glTable.setDefaultRenderer(String.class, renderer);
128         glTable.getInputMap().put(Globals.prefs.getKey("Delete"), "delete");
129         DeleteListener deleteListener = new DeleteListener();
130         glTable.getActionMap().put("delete", deleteListener);
131
132         selectionModel = new EventSelectionModel(sortedList);
133         glTable.setSelectionModel(selectionModel);
134         selectionModel.getSelected().addListEventListener(new EntrySelectionListener());
135         comparatorChooser = new TableComparatorChooser(glTable, sortedList,
136                 TableComparatorChooser.MULTIPLE_COLUMN_KEYBOARD);
137         setupComparatorChooser();
138         glTable.addMouseListener(new TableClickListener());
139
140
141         setWidths();
142
143         getContentPane().setLayout(new BorderLayout());
144         progressBar.setIndeterminate(true);
145         JPanel centerPan = new JPanel();
146         centerPan.setLayout(new BorderLayout());
147
148         //contentPane.setTopComponent(new JScrollPane(table));
149         contentPane.setTopComponent(new JScrollPane(glTable));
150         contentPane.setBottomComponent(new JScrollPane(preview));
151
152         centerPan.add(contentPane, BorderLayout.CENTER);
153         centerPan.add(progressBar, BorderLayout.SOUTH);
154
155         popup.add(deleteListener);
156         popup.addSeparator();
157         if (!newDatabase) {
158             GroupTreeNode node = metaData.getGroups();
159             groupsAdd.setEnabled(false); // Will get enabled if there are groups that can be added to.
160             insertNodes(groupsAdd, node, true);
161             popup.add(groupsAdd);
162         }
163
164         // Add "Attach file" menu choices to right click menu:
165         popup.add(new AttachFile("pdf"));
166         popup.add(new AttachFile("ps"));
167         popup.add(new AttachUrl());
168         getContentPane().add(centerPan, BorderLayout.CENTER);
169
170
171         ButtonBarBuilder bb = new ButtonBarBuilder();
172         bb.addGlue();
173         bb.addGridded(ok);
174         bb.addGridded(stop);
175         bb.addGridded(cancel);
176         bb.addRelatedGap();
177         bb.addGridded(help);
178         bb.addGlue();
179         bb.getPanel().setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
180
181         ButtonStackBuilder builder = new ButtonStackBuilder();
182         builder.addGridded(selectAll);
183         builder.addGridded(deselectAll);
184         builder.addRelatedGap();
185         builder.addGridded(delete);
186         builder.addRelatedGap();
187         builder.addGridded(autoGenerate);
188         builder.addGridded(generate);
189         builder.getPanel().setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
190         centerPan.add(builder.getPanel(), BorderLayout.WEST);
191
192         ok.setEnabled(false);
193         generate.setEnabled(false);
194         ok.addActionListener(new OkListener());
195         cancel.addActionListener(new CancelListener());
196         generate.addActionListener(new GenerateListener());
197         stop.addActionListener(new StopListener());
198         selectAll.addActionListener(new SelectionButton(true));
199         deselectAll.addActionListener(new SelectionButton(false));
200         delete.addActionListener(deleteListener);
201         help.addActionListener(new HelpAction(frame.helpDiag, GUIGlobals.importInspectionHelp));
202         getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);
203         setSize(new Dimension(650, 650));
204         //contentPane.setDividerLocation(0.6f);
205     }
206
207     public void setProgress(int current, int max) {
208         progressBar.setIndeterminate(false);
209         progressBar.setMinimum(0);
210         progressBar.setMaximum(max);
211         progressBar.setValue(current);
212     }
213
214     /**
215      * Wrapper for addEntries(List) that takes a single entry.
216      *
217      * @param entry The entry to add.
218      */
219     public void addEntry(BibtexEntry entry) {
220         List list = new ArrayList();
221         list.add(entry);
222         addEntries(list);
223     }
224
225     /**
226      * Add a List of entries to the table view. The table will update to show the
227      * added entries. Synchronizes on this.entries to avoid conflict with the delete button
228      * which removes entries.
229      *
230      * @param entries
231      */
232     public void addEntries(List entries) {
233
234         for (Iterator i = entries.iterator(); i.hasNext();) {
235             BibtexEntry entry = (BibtexEntry) i.next();
236             // We exploit the entry's search status for indicating "Keep" status:
237             entry.setSearchHit(defaultSelected);
238             // We exploit the entry's group status for indicating duplicate status:
239             if ((panel != null) && (Util.containsDuplicate(panel.database(), entry) != null)) {
240                 entry.setGroupHit(true);
241             }
242             this.entries.getReadWriteLock().writeLock().lock();
243             this.entries.add(entry);
244             this.entries.getReadWriteLock().writeLock().unlock();
245         }
246     }
247
248     /**
249      * Removes all selected entries from the table. Synchronizes on this.entries to prevent
250      * conflict with addition of new entries.
251      */
252     public void removeSelectedEntries() {
253         int row = glTable.getSelectedRow();
254         List toRemove = new ArrayList();
255         toRemove.addAll(selectionModel.getSelected());
256         entries.getReadWriteLock().writeLock().lock();
257         for (Iterator i=toRemove.iterator(); i.hasNext();) {
258             entries.remove(i.next());
259         }
260         entries.getReadWriteLock().writeLock().unlock();
261         glTable.clearSelection();
262         if ((row >= 0) && (entries.size() > 0)) {
263             row = Math.min(entries.size()-1, row);
264             glTable.addRowSelectionInterval(row, row);
265         }
266     }
267
268     /**
269      * When this method is called, the dialog will visually change to indicate
270      * that all entries are in place.
271      */
272     public void entryListComplete() {
273         progressBar.setIndeterminate(false);
274         progressBar.setVisible(false);
275         ok.setEnabled(true);
276         if (!generatedKeys)
277             generate.setEnabled(true);
278         stop.setEnabled(false);
279     }
280
281
282     /**
283      * This method returns a List containing all entries that are selected
284      * (checkbox checked).
285      *
286      * @return a List containing the selected entries.
287      */
288     public List getSelectedEntries() {
289         List selected = new ArrayList();
290         for (Iterator i=entries.iterator(); i.hasNext();) {
291             BibtexEntry entry = (BibtexEntry)i.next();
292             if (entry.isSearchHit())
293                 selected.add(entry);
294         }
295         /*for (int i = 0; i < table.getRowCount(); i++) {
296             Boolean sel = (Boolean) table.getValueAt(i, 0);
297             if (sel.booleanValue()) {
298                 selected.add(entries.get(i));
299             }
300         }*/
301         return selected;
302     }
303
304     /**
305      * Generate keys for all entries. All keys will be unique with respect to one another,
306      * and, if they are destined for an existing database, with respect to existing keys in
307      * the database.
308      */
309     public void generateKeys(boolean addColumn) {
310         entries.getReadWriteLock().writeLock().lock();
311         BibtexDatabase database = null;
312         // Relate to the existing database, if any:
313         if (panel != null)
314             database = panel.database();
315         // ... or create a temporary one:
316         else
317             database = new BibtexDatabase();
318         List keys = new ArrayList(entries.size());
319         // Iterate over the entries, add them to the database we are working with,
320         // and generate unique keys:
321         for (Iterator i = entries.iterator(); i.hasNext();) {
322             BibtexEntry entry = (BibtexEntry) i.next();
323             //if (newDatabase) {
324             try {
325                 entry.setId(Util.createNeutralId());
326                 database.insertEntry(entry);
327             } catch (KeyCollisionException ex) {
328                 ex.printStackTrace();
329             }
330             //}
331             LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, entry);
332             // Add the generated key to our list:
333             keys.add(entry.getCiteKey());
334         }
335         // Remove the entries from the database again, since they are not supposed to
336         // added yet. They only needed to be in it while we generated the keys, to keep
337         // control over key uniqueness.
338         for (Iterator i = entries.iterator(); i.hasNext();) {
339             BibtexEntry entry = (BibtexEntry) i.next();
340             database.removeEntry(entry.getId());
341         }
342         entries.getReadWriteLock().writeLock().lock();
343         glTable.repaint();
344     }
345
346
347     public void insertNodes(JMenu menu, GroupTreeNode node, boolean add) {
348         final AbstractAction action = getAction(node, add);
349
350         if (node.getChildCount() == 0) {
351             menu.add(action);
352             if (action.isEnabled())
353                 menu.setEnabled(true);
354             return;
355         }
356
357         JMenu submenu = null;
358         if (node.getGroup() instanceof AllEntriesGroup) {
359             for (int i = 0; i < node.getChildCount(); ++i) {
360                 insertNodes(menu, (GroupTreeNode) node.getChildAt(i), add);
361             }
362         } else {
363             submenu = new JMenu("[" + node.getGroup().getName() + "]");
364             // setEnabled(true) is done above/below if at least one menu
365             // entry (item or submenu) is enabled
366             submenu.setEnabled(action.isEnabled());
367             submenu.add(action);
368             submenu.add(new JPopupMenu.Separator());
369             for (int i = 0; i < node.getChildCount(); ++i)
370                 insertNodes(submenu, (GroupTreeNode) node.getChildAt(i), add);
371             menu.add(submenu);
372             if (submenu.isEnabled())
373                 menu.setEnabled(true);
374         }
375     }
376
377     private AbstractAction getAction(GroupTreeNode node, boolean add) {
378         AbstractAction action = add ? (AbstractAction) new AddToGroupAction(node)
379                 : (AbstractAction) new RemoveFromGroupAction(node);
380         AbstractGroup group = node.getGroup();
381         action.setEnabled(/*add ? */group.supportsAdd());// && !group.containsAll(selection)
382         //        : group.supportsRemove() && group.containsAny(selection));
383         return action;
384     }
385
386     /**
387      * Stores the information about the selected entries being scheduled for addition
388      * to this group. The entries are *not* added to the group at this time.
389      * <p/>
390      * Synchronizes on this.entries to prevent
391      * conflict with threads that modify the entry list.
392      */
393     class AddToGroupAction extends AbstractAction {
394         private GroupTreeNode node;
395
396         public AddToGroupAction(GroupTreeNode node) {
397             super(node.getGroup().getName());
398             this.node = node;
399         }
400
401         public void actionPerformed(ActionEvent event) {
402
403             selectionModel.getSelected().getReadWriteLock().writeLock().lock();
404             for (Iterator i=selectionModel.getSelected().iterator(); i.hasNext();) {
405                 BibtexEntry entry = (BibtexEntry)i.next();
406                 // We store the groups this entry should be added to in a Set in the Map:
407                 Set groups = (Set) groupAdditions.get(entry);
408                 if (groups == null) {
409                     // No previous definitions, so we create the Set now:
410                     groups = new HashSet();
411                     groupAdditions.put(entry, groups);
412                 }
413                 // Add the group:
414                 groups.add(node);
415             }
416             selectionModel.getSelected().getReadWriteLock().writeLock().unlock();
417         }
418     }
419
420     class RemoveFromGroupAction extends AbstractAction {
421         private GroupTreeNode node;
422
423         public RemoveFromGroupAction(GroupTreeNode node) {
424             this.node = node;
425         }
426
427         public void actionPerformed(ActionEvent event) {
428         }
429     }
430
431     public void addCallBack(CallBack cb) {
432         callBacks.add(cb);
433     }
434
435     class OkListener implements ActionListener {
436         public void actionPerformed(ActionEvent event) {
437
438             // First check if we are supposed to warn about duplicates. If so, see if there
439             // are unresolved duplicates, and warn if yes.
440             if (Globals.prefs.getBoolean("warnAboutDuplicatesInInspection")) {
441                 for (Iterator i=entries.iterator(); i.hasNext();) {
442
443                     BibtexEntry entry = (BibtexEntry)i.next();
444                     // Only check entries that are to be imported. Keep status is indicated
445                     // through the search hit status of the entry:
446                     if (!entry.isSearchHit())
447                         continue;
448
449                     // Check if the entry is a suspected, unresolved, duplicate. This status
450                     // is indicated by the entry's group hit status:
451                     if (entry.isGroupHit()) {
452                         CheckBoxMessage cbm = new CheckBoxMessage(
453                                 Globals.lang("There are possible duplicates (marked with a 'D' icon) that haven't been resolved. Continue?"),
454                                 Globals.lang("Disable this confirmation dialog"), false);
455                         int answer = JOptionPane.showConfirmDialog(ImportInspectionDialog.this, cbm, Globals.lang("Duplicates found"),
456                                     JOptionPane.YES_NO_OPTION);
457                         if (cbm.isSelected())
458                             Globals.prefs.putBoolean("warnAboutDuplicatesInInspection", false);
459                         if (answer == JOptionPane.NO_OPTION)
460                             return;
461                         break;
462                     }
463                 }
464             }
465
466             // The compund undo action used to contain all changes made by this dialog.
467             NamedCompound ce = new NamedCompound(undoName);
468
469             // See if we should remove any old entries for duplicate resolving:
470             if (entriesToDelete.size() > 0) {
471                 for (Iterator i=entriesToDelete.iterator(); i.hasNext();) {
472                     BibtexEntry entry = (BibtexEntry)i.next();
473                     ce.addEdit(new UndoableRemoveEntry(panel.database(), entry, panel));
474                     panel.database().removeEntry(entry.getId());
475                 }
476             }
477             /*panel.undoManager.addEdit(undo);
478             panel.refreshTable();
479             panel.markBaseChanged();*/
480
481
482             // If "Generate keys" is checked, generate keys unless it's already been done:
483             if (autoGenerate.isSelected() && !generatedKeys) {
484                 generateKeys(false);
485             }
486             // Remember the choice until next time:
487             Globals.prefs.putBoolean("generateKeysAfterInspection", autoGenerate.isSelected());
488
489             final List selected = getSelectedEntries();
490
491             if (selected.size() > 0) {
492
493                 if (newDatabase) {
494                     // Create a new BasePanel for the entries:
495                     BibtexDatabase base = new BibtexDatabase();
496                     panel = new BasePanel(frame, base, null, new HashMap(), Globals.prefs.get("defaultEncoding"));
497                 }
498
499                 boolean groupingCanceled = false;
500
501                 // Set owner/timestamp if options are enabled:
502                 Util.setAutomaticFields(selected);
503
504
505                 for (Iterator i = selected.iterator(); i.hasNext();) {
506                     BibtexEntry entry = (BibtexEntry) i.next();
507                     //entry.clone();
508
509                     // Remove settings to group/search hit status:
510                     entry.setSearchHit(false);
511                     entry.setGroupHit(false);
512
513                     // If this entry should be added to any groups, do it now:
514                     Set groups = (Set) groupAdditions.get(entry);
515                     if (!groupingCanceled && (groups != null)) {
516                         if (entry.getField(BibtexFields.KEY_FIELD) == null) {
517                             // The entry has no key, so it can't be added to the group.
518                             // The best course of ation is probably to ask the user if a key should be generated
519                             // immediately.
520                            int answer = JOptionPane.showConfirmDialog(ImportInspectionDialog.this,
521                                    Globals.lang("Cannot add entries to group without generating keys. Generate keys now?"),
522                                     Globals.lang("Add to group"), JOptionPane.YES_NO_OPTION);
523                             if (answer == JOptionPane.YES_OPTION) {
524                                 generateKeys(false);
525                             } else
526                                 groupingCanceled = true;
527                         }
528
529                         // If the key was list, or has been list now, go ahead:
530                         if (entry.getField(BibtexFields.KEY_FIELD) != null) {
531                             for (Iterator i2 = groups.iterator(); i2.hasNext();) {
532                                 GroupTreeNode node = (GroupTreeNode) i2.next();
533                                 if (node.getGroup().supportsAdd()) {
534                                     // Add the entry:
535                                     AbstractUndoableEdit undo = node.getGroup().add(new BibtexEntry[]{entry});
536                                     if (undo instanceof UndoableChangeAssignment)
537                                         ((UndoableChangeAssignment) undo).setEditedNode(node);
538                                     ce.addEdit(undo);
539
540                                 } else {
541                                     // Shouldn't happen...
542                                 }
543                             }
544                         }
545                     }
546
547                     try {
548                         entry.setId(Util.createNeutralId());
549                         panel.database().insertEntry(entry);
550                         ce.addEdit(new UndoableInsertEntry(panel.database(), entry, panel));
551                     } catch (KeyCollisionException e) {
552                         e.printStackTrace();
553                     }
554                 }
555
556                 ce.end();
557                 panel.undoManager.addEdit(ce);
558             }
559
560             dispose();
561             SwingUtilities.invokeLater(new Thread() {
562                 public void run() {
563                     if (newDatabase) {
564                         frame.addTab(panel, null, true);
565                     }
566                     panel.markBaseChanged();
567                     for (Iterator i = callBacks.iterator(); i.hasNext();) {
568                         ((CallBack) i.next()).done(selected.size());
569                     }
570                 }
571             });
572
573         }
574
575     }
576
577     private void signalStopFetching() {
578         for (Iterator i = callBacks.iterator(); i.hasNext();) {
579             ((CallBack) i.next()).stopFetching();
580         }
581     }
582
583     private void setWidths() {
584         DeleteListener deleteListener = new DeleteListener();
585         TableColumnModel cm = glTable.getColumnModel();
586         cm.getColumn(0).setPreferredWidth(55);
587         cm.getColumn(0).setMinWidth(55);
588         cm.getColumn(0).setMaxWidth(55);
589         for (int i = 1; i < PAD; i++) {
590             // Lock the width of icon columns.
591             cm.getColumn(i).setPreferredWidth(GUIGlobals.WIDTH_ICON_COL);
592             cm.getColumn(i).setMinWidth(GUIGlobals.WIDTH_ICON_COL);
593             cm.getColumn(i).setMaxWidth(GUIGlobals.WIDTH_ICON_COL);
594         }
595
596         for (int i = 0; i < fields.length; i++) {
597             int width = BibtexFields.getFieldLength( fields[i]) ;
598             glTable.getColumnModel().getColumn(i + PAD).setPreferredWidth(width);
599         }
600     }
601
602
603
604     class StopListener implements ActionListener {
605         public void actionPerformed(ActionEvent event) {
606             signalStopFetching();
607             entryListComplete();
608         }
609     }
610
611     class CancelListener implements ActionListener {
612         public void actionPerformed(ActionEvent event) {
613             signalStopFetching();
614             dispose();
615             for (Iterator i = callBacks.iterator(); i.hasNext();) {
616                 ((CallBack) i.next()).cancelled();
617             }
618         }
619     }
620
621     class GenerateListener implements ActionListener {
622         public void actionPerformed(ActionEvent event) {
623             generate.setEnabled(false);
624             generatedKeys = true; // To prevent the button from getting enabled again.
625             generateKeys(true); // Generate the keys.
626         }
627     }
628
629     class DeleteListener extends AbstractAction implements ActionListener {
630         public DeleteListener() {
631             super(Globals.lang("Delete"), new ImageIcon(GUIGlobals.removeIconFile));
632         }
633
634         public void actionPerformed(ActionEvent event) {
635             removeSelectedEntries();
636         }
637     }
638
639     class MyTable extends JTable {
640         public MyTable(TableModel model) {
641             super(model);
642             //setDefaultRenderer(Boolean.class, );
643         }
644
645         public boolean isCellEditable(int row, int col) {
646             return col == 0;
647         }
648     }
649
650     class MyTableModel extends DefaultTableModel {
651
652
653         public Class getColumnClass(int i) {
654             if (i == 0)
655                 return Boolean.class;
656             else
657                 return String.class;
658         }
659
660     }
661
662     class SelectionButton implements ActionListener {
663         private Boolean enable;
664
665         public SelectionButton(boolean enable) {
666             this.enable = Boolean.valueOf(enable);
667         }
668
669         public void actionPerformed(ActionEvent event) {
670             for (int i = 0; i < glTable.getRowCount(); i++) {
671                 glTable.setValueAt(enable, i, 0);
672             }
673             glTable.repaint();
674         }
675     }
676
677     class EntrySelectionListener implements ListEventListener {
678
679         public void listChanged(ListEvent listEvent) {
680             if (listEvent.getSourceList().size() == 1) {
681                 preview.setEntry((BibtexEntry) listEvent.getSourceList().get(0));
682                 contentPane.setDividerLocation(0.5f);
683                 SwingUtilities.invokeLater(new Runnable() {
684                     public void run() {
685                         preview.scrollRectToVisible(toRect);
686                     }
687                 });
688             }
689         }
690     }
691
692
693     /**
694      * This class handles clicks on the table that should trigger specific
695      * events, like opening the popup menu.
696      */
697     class TableClickListener implements MouseListener {
698
699         public void mouseClicked(MouseEvent e) {
700
701         }
702
703         public void mouseEntered(MouseEvent e) {
704
705         }
706
707         public void mouseExited(MouseEvent e) {
708
709         }
710
711         public void mouseReleased(MouseEvent e) {
712             // Check if the user has right-clicked. If so, open the right-click menu.
713             if (e.isPopupTrigger()) {
714                 int[] rows = glTable.getSelectedRows();
715                 popup.show(glTable, e.getX(), e.getY());
716                 return;
717             }
718         }
719
720         public void mousePressed(MouseEvent e) {
721             // Check if the user has right-clicked. If so, open the right-click menu.
722             if (e.isPopupTrigger()) {
723                 int[] rows = glTable.getSelectedRows();
724                 popup.show(glTable, e.getX(), e.getY());
725                 return;
726             }
727
728             // Check if any other action should be taken:
729             final int col = glTable.columnAtPoint(e.getPoint()),
730               row = glTable.rowAtPoint(e.getPoint());
731             // Is this the duplicate icon column, and is there an icon?
732             if ((col == DUPL_COL) && (glTable.getValueAt(row, col) != null)) {
733                 BibtexEntry first = (BibtexEntry)sortedList.get(row);
734                 BibtexEntry other = Util.containsDuplicate(panel.database(), first);
735                 if (other != null) { // This should be true since the icon is displayed...
736                     DuplicateResolverDialog diag = new DuplicateResolverDialog(frame, other, first, DuplicateResolverDialog.INSPECTION);
737                     Util.placeDialog(diag, ImportInspectionDialog.this);
738                     diag.setVisible(true);
739                     ImportInspectionDialog.this.toFront();
740                     if (diag.getSelected() == DuplicateResolverDialog.KEEP_UPPER) {
741                         // Remove old entry. Or... add it to a list of entries to be deleted. We only delete
742                         // it after Ok is clicked.
743                         entriesToDelete.add(other);
744                         // Clear duplicate icon, which is controlled by the group hit
745                         // field of the entry:
746                         entries.getReadWriteLock().writeLock().lock();
747                         first.setGroupHit(false);
748                         entries.getReadWriteLock().writeLock().unlock();
749
750                     } else if (diag.getSelected() == DuplicateResolverDialog.KEEP_LOWER) {
751                         // Remove the entry from the import inspection dialog.
752                         entries.getReadWriteLock().writeLock().lock();
753                         entries.remove(first);
754                         entries.getReadWriteLock().writeLock().unlock();
755                     } else if (diag.getSelected() == DuplicateResolverDialog.KEEP_BOTH) {
756                         // Do nothing.
757                         entries.getReadWriteLock().writeLock().lock();
758                         first.setGroupHit(false);
759                         entries.getReadWriteLock().writeLock().unlock();
760                     }
761                 }
762             }
763         }
764     }
765
766     class AttachUrl extends JMenuItem implements ActionListener {
767         public AttachUrl() {
768             super(Globals.lang("Attach URL"));
769             addActionListener(this);
770         }
771
772         public void actionPerformed(ActionEvent event) {
773             if (selectionModel.getSelected().size() != 1)
774                 return;
775             BibtexEntry entry = (BibtexEntry) selectionModel.getSelected().get(0);
776             String result = JOptionPane.showInputDialog(ths, Globals.lang("Enter URL"), entry.getField("url"));
777             entries.getReadWriteLock().writeLock().lock();
778             if (result != null) {
779                 if (result.equals("")) {
780                     entry.clearField("url");
781                 } else {
782                     entry.setField("url", result);
783                 }
784             }
785             entries.getReadWriteLock().writeLock().unlock();
786             glTable.repaint();
787         }
788     }
789
790
791     class AttachFile extends JMenuItem implements ActionListener {
792         String fileType;
793
794         public AttachFile(String fileType) {
795             super(Globals.lang("Attach %0 file", new String[]{fileType.toUpperCase()}));
796             this.fileType = fileType;
797             addActionListener(this);
798         }
799
800         public void actionPerformed(ActionEvent event) {
801
802             if (selectionModel.getSelected().size() != 1)
803                 return;
804             BibtexEntry entry = (BibtexEntry) selectionModel.getSelected().get(0);
805             // Call up a dialog box that provides Browse, Download and auto buttons:
806             AttachFileDialog diag = new AttachFileDialog(ths, metaData, entry, fileType);
807             Util.placeDialog(diag, ths);
808             diag.setVisible(true);
809             // After the dialog has closed, if it wasn't cancelled, list the field:
810             if (!diag.cancelled()) {
811                 entries.getReadWriteLock().writeLock().lock();
812                 entry.setField(fileType, diag.getValue());
813                 entries.getReadWriteLock().writeLock().unlock();
814                 glTable.repaint();
815             }
816
817         }
818     }
819
820     public static interface CallBack {
821         // This method is called by the dialog when the user has selected the
822         // wanted entries, and clicked Ok. The callback object can update status
823         // line etc.
824         public void done(int entriesImported);
825
826         // This method is called by the dialog when the user has cancelled the import.
827         public void cancelled();
828
829         // This method is called by the dialog when the user has cancelled or
830         // signalled a stop. It is expected that any long-running fetch operations
831         // will stop after this method is called.
832         public void stopFetching();
833     }
834
835
836     private void setupComparatorChooser() {
837         // First column:
838         java.util.List comparators = comparatorChooser.getComparatorsForColumn(0);
839         comparators.clear();
840
841         comparators = comparatorChooser.getComparatorsForColumn(1);
842         comparators.clear();
843
844         // Icon columns:
845         for (int i = 2; i < PAD; i++) {
846             comparators = comparatorChooser.getComparatorsForColumn(i);
847             comparators.clear();
848             if (i == PDF_COL)
849                 comparators.add(new IconComparator(new String[] {"pdf"}));
850             else if (i == PS_COL)
851                 comparators.add(new IconComparator(new String[] {"ps"}));
852             else if (i == URL_COL)
853                 comparators.add(new IconComparator(new String[] {"url"}));
854
855         }
856         // Remaining columns:
857         for (int i = PAD; i < PAD+fields.length; i++) {
858             comparators = comparatorChooser.getComparatorsForColumn(i);
859             comparators.clear();
860             comparators.add(new FieldComparator(fields[i-PAD]));
861         }
862
863         // Set initial sort columns:
864
865         /*// Default sort order:
866         String[] sortFields = new String[] {Globals.prefs.get("priSort"), Globals.prefs.get("secSort"),
867             Globals.prefs.get("terSort")};
868         boolean[] sortDirections = new boolean[] {Globals.prefs.getBoolean("priDescending"),
869             Globals.prefs.getBoolean("secDescending"), Globals.prefs.getBoolean("terDescending")}; // descending
870         */
871         sortedList.getReadWriteLock().writeLock().lock();
872         comparatorChooser.appendComparator(PAD, 0, false);
873         sortedList.getReadWriteLock().writeLock().unlock();
874
875     }
876
877     class EntryTable extends JTable {
878         GeneralRenderer renderer = new GeneralRenderer(Color.white, true);
879         public EntryTable(TableModel model) {
880             super(model);
881         }
882         public TableCellRenderer getCellRenderer(int row, int column) {
883             return column == 0 ? getDefaultRenderer(Boolean.class) : renderer;
884         }
885
886         /*public TableCellEditor getCellEditor() {
887             return getDefaultEditor(Boolean.class);
888         } */
889
890         public Class getColumnClass(int col) {
891             if (col == 0)
892                 return Boolean.class;
893             else if (col < PAD)
894                 return JLabel.class;
895             else return String.class;
896         }
897
898         public boolean isCellEditable(int row, int column) {
899             return column == 0;
900         }
901
902         public void setValueAt(Object value, int row, int column) {
903             // Only column 0, which is controlled by BibtexEntry.searchHit, is editable:
904             entries.getReadWriteLock().writeLock().lock();
905             BibtexEntry entry = (BibtexEntry)sortedList.get(row);
906             entry.setSearchHit(((Boolean)value).booleanValue());
907             entries.getReadWriteLock().writeLock().unlock();
908         }
909     }
910
911     class EntryTableFormat implements TableFormat {
912         public int getColumnCount() {
913             return PAD+fields.length;
914         }
915
916         public String getColumnName(int i) {
917             if (i == 0)
918                 return Globals.lang("Keep");
919             if (i >= PAD) {
920                 return Util.nCase(fields[i-PAD]);
921             }
922             return "";
923         }
924
925         public Object getColumnValue(Object object, int i) {
926             BibtexEntry entry = (BibtexEntry)object;
927             if (i == 0)
928                 return entry.isSearchHit() ? Boolean.TRUE : Boolean.FALSE;
929             else if (i < PAD) {
930                 Object o;
931                 switch (i) {
932                     case DUPL_COL: return entry.isGroupHit() ?  duplLabel : null;
933                     case PDF_COL:
934                         o = entry.getField("pdf");
935                         if (o != null) {
936                             pdfLabel.setToolTipText((String)o);
937                             return pdfLabel;
938                         } else return null;
939
940                     case PS_COL:
941                         o = entry.getField("ps");
942                         if (o != null) {
943                             psLabel.setToolTipText((String)o);
944                             return psLabel;
945                         } else return null;
946                     case URL_COL:
947                         o = entry.getField("url");
948                         if (o != null) {
949                             urlLabel.setToolTipText((String)o);
950                             return urlLabel;
951                         } else return null;
952                     default: return null;
953                 }
954             }
955             else {
956                 String field = fields[i-PAD];
957                 if (field.equals("author") || field.equals("editor")) {
958                     String contents = (String)entry.getField(field);
959                     return (contents != null) ?
960                         AuthorList.fixAuthor_Natbib(contents) : "";
961                 }
962                 else
963                     return entry.getField(field);
964             }
965         }
966
967     }
968 }