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