0c6dc018575410de189406a538346d5f417d1cd3
[debian/jabref.git] / src / java / net / sf / jabref / gui / MainTable.java
1 package net.sf.jabref.gui;
2
3 import net.sf.jabref.*;
4 import net.sf.jabref.search.SearchMatcher;
5 import net.sf.jabref.search.HitOrMissComparator;
6 import net.sf.jabref.groups.EntryTableTransferHandler;
7
8 import javax.swing.*;
9 import javax.swing.plaf.TableUI;
10 import javax.swing.table.TableCellRenderer;
11 import javax.swing.table.TableModel;
12 import javax.swing.table.TableColumnModel;
13
14 import ca.odell.glazedlists.SortedList;
15 import ca.odell.glazedlists.EventList;
16 import ca.odell.glazedlists.gui.AbstractTableComparatorChooser;
17 import ca.odell.glazedlists.matchers.Matcher;
18 import ca.odell.glazedlists.event.ListEventListener;
19 import ca.odell.glazedlists.swing.EventSelectionModel;
20 import ca.odell.glazedlists.swing.TableComparatorChooser;
21 import ca.odell.glazedlists.swing.EventTableModel;
22
23 import java.awt.*;
24 import java.awt.event.ActionListener;
25 import java.awt.event.ActionEvent;
26 import java.util.Comparator;
27
28 /**
29  * The central table which displays the bibtex entries.
30  * 
31  * User: alver
32  * Date: Oct 12, 2005
33  * Time: 10:29:39 PM
34  * 
35  */
36 public class MainTable extends JTable {
37         
38     private MainTableFormat tableFormat;
39     private SortedList sortedForMarking, sortedForTable, sortedForSearch, sortedForGrouping;
40     private boolean tableColorCodes, showingFloatSearch=false, showingFloatGrouping=false;
41     private EventSelectionModel selectionModel;
42     private TableComparatorChooser comparatorChooser;
43     private JScrollPane pane;
44     private Comparator searchComparator, groupComparator,
45             markingComparator = new IsMarkedComparator();
46     private Matcher searchMatcher, groupMatcher;
47
48     // Constants used to define how a cell should be rendered.
49     public static final int REQUIRED = 1, OPTIONAL = 2,
50       REQ_STRING = 1,
51       REQ_NUMBER = 2,
52       OPT_STRING = 3,
53       OTHER = 3,
54       BOOLEAN = 4,
55       ICON_COL = 8; // Constant to indicate that an icon cell renderer should be used.
56
57     static {
58         updateRenderers();
59     }
60
61
62     public MainTable(MainTableFormat tableFormat, EventList list, JabRefFrame frame,
63                      BasePanel panel) {
64         super();
65         this.tableFormat = tableFormat;
66         // This SortedList has a Comparator controlled by the TableComparatorChooser
67         // we are going to install, which responds to user sorting selctions:
68         sortedForTable = new SortedList(list, null);
69         // This SortedList applies afterwards, and floats marked entries:
70         sortedForMarking = new SortedList(sortedForTable, null);
71         // This SortedList applies afterwards, and can float search hits:
72         sortedForSearch = new SortedList(sortedForMarking, null);
73         // This SortedList applies afterwards, and can float grouping hits:
74         sortedForGrouping = new SortedList(sortedForSearch, null);
75
76
77         searchMatcher = null;
78         groupMatcher = null;
79         searchComparator = null;//new HitOrMissComparator(searchMatcher);
80         groupComparator = null;//new HitOrMissComparator(groupMatcher);
81
82         EventTableModel tableModel = new EventTableModel(sortedForGrouping, tableFormat);
83         setModel(tableModel);
84
85         tableColorCodes = Globals.prefs.getBoolean("tableColorCodesOn");
86         selectionModel = new EventSelectionModel(sortedForGrouping);
87         setSelectionModel(selectionModel);
88         pane = new JScrollPane(this);
89         pane.getViewport().setBackground(Globals.prefs.getColor("tableBackground"));
90         setGridColor(Globals.prefs.getColor("gridColor"));
91         comparatorChooser = new MyTableComparatorChooser(this, sortedForTable,
92                 TableComparatorChooser.MULTIPLE_COLUMN_KEYBOARD);
93         
94         final EventList selected = getSelected();
95
96         // enable DnD
97         setDragEnabled(true);
98         TransferHandler xfer = new EntryTableTransferHandler(this, frame, panel);
99         setTransferHandler(xfer);
100         pane.setTransferHandler(xfer);
101
102         setupComparatorChooser();
103         refreshSorting();
104         setWidths();
105
106     }
107
108     public void refreshSorting() {
109         sortedForMarking.getReadWriteLock().writeLock().lock();
110         if (Globals.prefs.getBoolean("floatMarkedEntries"))
111             sortedForMarking.setComparator(markingComparator);
112         else
113             sortedForMarking.setComparator(null);
114         sortedForMarking.getReadWriteLock().writeLock().unlock();
115         sortedForSearch.getReadWriteLock().writeLock().lock();
116         sortedForSearch.setComparator(searchComparator);
117         sortedForSearch.getReadWriteLock().writeLock().unlock();
118         sortedForGrouping.getReadWriteLock().writeLock().lock();
119         sortedForGrouping.setComparator(groupComparator);
120         sortedForGrouping.getReadWriteLock().writeLock().unlock();
121     }
122
123     /**
124      * Adds a sorting rule that floats hits to the top, and causes non-hits to be grayed out:
125      * @param m The Matcher that determines if an entry is a hit or not.
126      */
127     public void showFloatSearch(Matcher m) {
128         showingFloatSearch = true;
129         searchMatcher = m;
130         searchComparator = new HitOrMissComparator(m);
131         refreshSorting();
132         scrollTo(0);
133     }
134
135     /**
136      * Removes sorting by search results, and graying out of non-hits.
137      */
138     public void stopShowingFloatSearch() {
139         showingFloatSearch = false;
140         searchMatcher = null;
141         searchComparator = null;
142         refreshSorting();
143     }
144
145     /**
146      * Adds a sorting rule that floats group hits to the top, and causes non-hits to be grayed out:
147      * @param m The Matcher that determines if an entry is a in the current group selection or not.
148      */
149     public void showFloatGrouping(Matcher m) {
150         showingFloatGrouping = true;
151         groupMatcher = m;
152         groupComparator = new HitOrMissComparator(m);
153         refreshSorting();
154     }
155
156     /**
157      * Removes sorting by group, and graying out of non-hits.
158      */
159     public void stopShowingFloatGrouping() {
160         showingFloatGrouping = false;
161         groupMatcher = null;
162         groupComparator = null;
163         refreshSorting();
164     }
165
166     public EventList getTableRows() {
167         return sortedForGrouping;
168     }
169     public void addSelectionListener(ListEventListener listener) {
170         getSelected().addListEventListener(listener);
171     }
172
173     public JScrollPane getPane() {
174         return pane;
175     }
176
177     public TableCellRenderer getCellRenderer(int row, int column) {
178
179         int score = -3;
180         TableCellRenderer renderer = defRenderer;
181
182         int status = getCellStatus(row, column);
183
184         if (!showingFloatSearch || matches(row, searchMatcher))
185             score++;
186         if (!showingFloatGrouping || matches(row, groupMatcher))
187             score += 2;
188
189         // Now, a grayed out renderer is for entries with -1, and
190         // a very grayed out one for entries with -2
191         if (score < -1) {
192             if (column == 0) {
193                 veryGrayedOutNumberRenderer.setNumber(row);
194                 renderer = veryGrayedOutNumberRenderer;
195             } else renderer = veryGrayedOutRenderer;
196         }
197         else if (score == -1) {
198             if (column == 0) {
199                 grayedOutNumberRenderer.setNumber(row);
200                 renderer = grayedOutNumberRenderer;
201             } else renderer = grayedOutRenderer;
202         }
203
204         else if (column == 0) {
205             // Return a renderer with red background if the entry is incomplete.
206             if (!isComplete(row)) {
207                 incRenderer.setNumber(row);
208                 renderer = incRenderer;
209             } else {
210                 compRenderer.setNumber(row);
211                 if (isMarked(row)) {
212                     renderer = markedNumberRenderer;
213                     markedNumberRenderer.setNumber(row);
214                 } else
215                     renderer = compRenderer;
216             }
217         }
218         else if (tableColorCodes) {
219             if (status == REQUIRED)
220                 renderer = reqRenderer;
221             else if (status == OPTIONAL)
222                 renderer = optRenderer;
223             else if (status == BOOLEAN)
224                 renderer = getDefaultRenderer(Boolean.class);
225         }
226
227         // For MARKED feature:
228         if ((column != 0) && isMarked(row)) {
229             renderer = markedRenderer;
230         }
231
232         return renderer;
233
234     }
235
236     public void setWidths() {
237         // Setting column widths:
238         int ncWidth = Globals.prefs.getInt("numberColWidth");
239         String[] widths = Globals.prefs.getStringArray("columnWidths");
240         TableColumnModel cm = getColumnModel();
241         cm.getColumn(0).setPreferredWidth(ncWidth);
242         for (int i = 1; i < tableFormat.padleft; i++) {
243             // Lock the width of icon columns.
244             cm.getColumn(i).setPreferredWidth(GUIGlobals.WIDTH_ICON_COL);
245             cm.getColumn(i).setMinWidth(GUIGlobals.WIDTH_ICON_COL);
246             cm.getColumn(i).setMaxWidth(GUIGlobals.WIDTH_ICON_COL);
247         }
248         for (int i = tableFormat.padleft; i < getModel().getColumnCount(); i++) {
249             try {
250                 cm.getColumn(i).setPreferredWidth(Integer.parseInt(widths[i - tableFormat.padleft]));
251             } catch (Throwable ex) {
252                 Globals.logger("Exception while setting column widths. Choosing default.");
253                 cm.getColumn(i).setPreferredWidth(GUIGlobals.DEFAULT_FIELD_LENGTH);
254             }
255
256         }
257     }
258
259     public BibtexEntry getEntryAt(int row) {
260         return (BibtexEntry)sortedForGrouping.get(row);
261     }
262
263     public BibtexEntry[] getSelectedEntries() {
264         final BibtexEntry[] BE_ARRAY = new BibtexEntry[0];
265         return (BibtexEntry[]) getSelected().toArray(BE_ARRAY);
266     }
267
268     /**
269      * This method sets up what Comparators are used for the various table columns.
270      * The ComparatorChooser enables and disables such Comparators as the user clicks
271      * columns, but this is where the Comparators are defined. Also, the ComparatorChooser
272      * is initialized with the sort order defined in Preferences.
273      */
274     private void setupComparatorChooser() {
275         // First column:
276         java.util.List comparators = comparatorChooser.getComparatorsForColumn(0);
277         comparators.clear();
278         comparators.add(new FirstColumnComparator());
279
280         // Icon columns:
281         for (int i = 1; i < tableFormat.padleft; i++) {
282             comparators = comparatorChooser.getComparatorsForColumn(i);
283             comparators.clear();
284             String[] iconField = tableFormat.getIconTypeForColumn(i);
285             comparators.add(new IconComparator(iconField));
286         }
287         // Remaining columns:
288         for (int i = tableFormat.padleft; i < tableFormat.getColumnCount(); i++) {
289             comparators = comparatorChooser.getComparatorsForColumn(i);
290             comparators.clear();
291             comparators.add(new FieldComparator(tableFormat.getColumnName(i).toLowerCase()));
292         }
293
294         // Set initial sort columns:
295
296         // Default sort order:
297         String[] sortFields = new String[] {Globals.prefs.get("priSort"), Globals.prefs.get("secSort"),
298             Globals.prefs.get("terSort")};
299         boolean[] sortDirections = new boolean[] {Globals.prefs.getBoolean("priDescending"),
300             Globals.prefs.getBoolean("secDescending"), Globals.prefs.getBoolean("terDescending")}; // descending
301
302         sortedForTable.getReadWriteLock().writeLock().lock();
303         for (int i=0; i<sortFields.length; i++) {
304             int index = tableFormat.getColumnIndex(sortFields[i]);
305             if (index >= 0) {
306                 comparatorChooser.appendComparator(index, 0, sortDirections[i]);
307             }
308         }
309         sortedForTable.getReadWriteLock().writeLock().unlock();
310
311     }
312
313     public int getCellStatus(int row, int col) {
314         try {
315             BibtexEntry be = (BibtexEntry)sortedForGrouping.get(row);
316             BibtexEntryType type = be.getType();
317             String columnName = tableFormat.getColumnName(col).toLowerCase();
318             if (columnName.equals(BibtexFields.KEY_FIELD) || type.isRequired(columnName)) {
319                 return REQUIRED;
320             }
321             if (type.isOptional(columnName)) {
322                 return OPTIONAL;
323             }
324             return OTHER;
325         } catch (NullPointerException ex) {
326             //System.out.println("Exception: getCellStatus");
327             return OTHER;
328         }
329     }
330
331     public EventList getSelected() {
332         return selectionModel.getSelected();
333     }
334
335     public int findEntry(BibtexEntry entry) {
336         //System.out.println(sortedForGrouping.indexOf(entry));
337         return sortedForGrouping.indexOf(entry);
338     }
339
340     public String[] getIconTypeForColumn(int column) {
341         return tableFormat.getIconTypeForColumn(column);
342     }
343
344     private boolean nonZeroField(int row, String field) {
345         BibtexEntry be = (BibtexEntry)sortedForGrouping.get(row);
346         Object o = be.getField(field);
347         return ((o == null) || !o.equals("0"));
348     }
349
350     private boolean matches(int row, Matcher m) {
351         Object o = sortedForGrouping.get(row);
352         return m.matches(o);
353     }
354
355     private boolean isComplete(int row) {
356         try {
357             BibtexEntry be = (BibtexEntry)sortedForGrouping.get(row);
358             return be.hasAllRequiredFields();
359         } catch (NullPointerException ex) {
360             //System.out.println("Exception: isComplete");
361             return true;
362         }
363     }
364
365     private boolean isMarked(int row) {
366         try {
367             BibtexEntry be = (BibtexEntry)sortedForGrouping.get(row);
368             return Util.isMarked(be);
369         } catch (NullPointerException ex) {
370             //System.out.println("Exception: isMarked");
371             return false;
372         }
373     }
374
375
376     public void scrollTo(int y) {
377         JScrollBar scb = pane.getVerticalScrollBar();
378         scb.setValue(y * scb.getUnitIncrement(1));
379     }
380
381     /**
382      * updateFont
383      */
384     public void updateFont() {
385         setFont(GUIGlobals.CURRENTFONT);
386         setRowHeight(GUIGlobals.TABLE_ROW_PADDING + GUIGlobals.CURRENTFONT.getSize());
387     }
388
389     public void ensureVisible(int row) {
390         JScrollBar vert = pane.getVerticalScrollBar();
391         int y = row * getRowHeight();
392         if ((y < vert.getValue()) || (y > vert.getValue() + vert.getVisibleAmount()))
393             scrollToCenter(row, 1);
394     }
395
396     public void scrollToCenter(int rowIndex, int vColIndex) {
397         if (!(this.getParent() instanceof JViewport)) {
398             return;
399         }
400
401         JViewport viewport = (JViewport) this.getParent();
402
403         // This rectangle is relative to the table where the
404         // northwest corner of cell (0,0) is always (0,0).
405         Rectangle rect = this.getCellRect(rowIndex, vColIndex, true);
406
407         // The location of the view relative to the table
408         Rectangle viewRect = viewport.getViewRect();
409
410         // Translate the cell location so that it is relative
411         // to the view, assuming the northwest corner of the
412         // view is (0,0).
413         rect.setLocation(rect.x - viewRect.x, rect.y - viewRect.y);
414
415         // Calculate location of rect if it were at the center of view
416         int centerX = (viewRect.width - rect.width) / 2;
417         int centerY = (viewRect.height - rect.height) / 2;
418
419         // Fake the location of the cell so that scrollRectToVisible
420         // will move the cell to the center
421         if (rect.x < centerX) {
422             centerX = -centerX;
423         }
424         if (rect.y < centerY) {
425             centerY = -centerY;
426         }
427         rect.translate(centerX, centerY);
428
429         // Scroll the area into view.
430         viewport.scrollRectToVisible(rect);
431
432         revalidate();
433         repaint();
434     }
435
436
437     private static GeneralRenderer defRenderer
438     ,
439     reqRenderer
440     ,
441     optRenderer
442     ,
443     grayedOutRenderer,
444     veryGrayedOutRenderer
445     ,
446     markedRenderer;
447
448     private static IncompleteRenderer incRenderer;
449     private static CompleteRenderer
450             compRenderer,
451             grayedOutNumberRenderer,
452             veryGrayedOutNumberRenderer,
453             markedNumberRenderer;
454
455     public static void updateRenderers() {
456
457         defRenderer = new GeneralRenderer(Globals.prefs.getColor("tableBackground"),
458                 Globals.prefs.getColor("tableText"));
459         reqRenderer = new GeneralRenderer(Globals.prefs.getColor("tableReqFieldBackground"), Globals.prefs.getColor("tableText"));
460         optRenderer = new GeneralRenderer(Globals.prefs.getColor("tableOptFieldBackground"), Globals.prefs.getColor("tableText"));
461         incRenderer = new IncompleteRenderer();
462         compRenderer = new CompleteRenderer(Globals.prefs.getColor("tableBackground"));
463         markedNumberRenderer = new CompleteRenderer(Globals.prefs.getColor("markedEntryBackground"));
464         grayedOutNumberRenderer = new CompleteRenderer(Globals.prefs.getColor("grayedOutBackground"));
465         veryGrayedOutNumberRenderer = new CompleteRenderer(Globals.prefs.getColor("veryGrayedOutBackground"));
466         grayedOutRenderer = new GeneralRenderer(Globals.prefs.getColor("grayedOutBackground"),
467             Globals.prefs.getColor("grayedOutText"));
468         veryGrayedOutRenderer = new GeneralRenderer(Globals.prefs.getColor("veryGrayedOutBackground"),
469                 Globals.prefs.getColor("veryGrayedOutText"));
470         markedRenderer = new GeneralRenderer(Globals.prefs.getColor("markedEntryBackground"),
471                 Globals.prefs.getColor("tableText"));
472     }
473
474     static class IncompleteRenderer extends GeneralRenderer {
475         public IncompleteRenderer() {
476             super(Globals.prefs.getColor("incompleteEntryBackground"));
477             super.setToolTipText(Globals.lang("This entry is incomplete"));
478         }
479
480         protected void setNumber(int number) {
481             super.setValue(String.valueOf(number + 1));
482         }
483
484         protected void setValue(Object value) {
485
486         }
487     }
488
489     static class CompleteRenderer extends GeneralRenderer {
490         public CompleteRenderer(Color color) {
491             super(color);
492         }
493
494         protected void setNumber(int number) {
495             super.setValue(String.valueOf(number + 1));
496         }
497
498         protected void setValue(Object value) {
499
500         }
501     }
502
503     class MyTableComparatorChooser extends TableComparatorChooser {
504         public MyTableComparatorChooser(JTable table, SortedList list,
505                                         Object sortingStrategy) {
506             super(table, list, sortingStrategy);
507             // We need to reset the stack of sorted list each time sorting order
508             // changes, or the sorting breaks down:
509             addSortActionListener(new ActionListener() {
510                 public void actionPerformed(ActionEvent e) {
511                     //System.out.println("...");
512                     refreshSorting();
513                 }
514             });
515         }
516 /*
517         protected void columnClicked(int i, int i1) {
518
519             super.columnClicked(i, i1);
520             refreshSorting();
521         }*/
522     }
523
524     /**
525      * Morten Alver: This override is a workaround NullPointerException when
526      * dragging stuff into the table. I found this in a forum, but have no idea
527      * why it works.
528      * @param newUI
529      */
530     public void setUI(TableUI newUI) {
531         super.setUI(newUI);
532         TransferHandler handler = getTransferHandler();
533         setTransferHandler(null);
534         setTransferHandler(handler);
535
536     }
537
538     /**
539      * Get the first comparator set up for the given column.
540      * @param index The column number.
541      * @return The Comparator, or null if none is set.
542      */
543     public Comparator getComparatorForColumn(int index) {
544         java.util.List l = comparatorChooser.getComparatorsForColumn(index);
545         return l.size() == 0 ? null : (Comparator)l.get(0);
546     }
547
548     /**
549      * Find out which column is set as sort column.
550      * @param number The position in the sort hierarchy (primary, secondary, etc.)
551      * @return The sort column number.
552      */
553     public int getSortingColumn(int number) {
554         java.util.List l = comparatorChooser.getSortingColumns();
555         if (l.size() <= number)
556             return -1;
557         else
558             return ((Integer)l.get(number)).intValue();
559     }
560
561     /**
562      * Returns the List of entries sorted by a user-selected term. This is the
563      * sorting before marking, search etc. applies.
564      *
565      * Note: The returned List must not be modified from the outside
566      * @return The sorted list of entries.
567      */
568     public SortedList getSortedForTable() {
569         return sortedForTable;
570     }
571 }