b90389c6443b6da19667900c0ee8bfcf2b12a711
[debian/jabref.git] / src / java / net / sf / jabref / EntryTable.java
1 /*
2 Copyright (C) 2003 Nizar N. Batada, Morten O. Alver
3
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
6 described below.
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA
22
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
25
26 */
27
28 package net.sf.jabref;
29
30 import java.awt.*;
31 import java.awt.event.*;
32 import java.io.IOException;
33 import java.util.Set;
34 import java.util.HashSet;
35
36 import javax.swing.*;
37 import javax.swing.border.Border;
38 import javax.swing.event.*;
39 import javax.swing.plaf.basic.BasicTableUI;
40 import javax.swing.table.*;
41
42 import net.sf.jabref.groups.EntryTableTransferHandler;
43
44 public class EntryTable extends JTable {
45
46     final int PREFERRED_WIDTH = 400, PREFERRED_HEIGHT = 30;
47
48     // We use a subclassed JScrollPane with setBorder() overridden as
49     // a no-op. This is done to avoid the JTable setting its border,
50     // which it does whether we want it or not. And we don't. :)
51     JScrollPane sp = new JScrollPane((JTable)this) {
52             public void setBorder(Border b) {}
53         };
54
55     JPopupMenu rightClickMenu = null;
56     EntryTableModel tableModel;
57     JabRefPreferences prefs;
58     protected boolean showingSearchResults = false,
59         showingGroup = false;
60     private boolean antialiasing = Globals.prefs.getBoolean("antialias"),
61         ctrlClick = false,
62         selectionListenerOn = true,
63         tableColorCodes = true;
64     //RenderingHints renderingHints;
65     private BasePanel panel;
66     Set lastSelection = new HashSet();
67
68     private ListSelectionListener previewListener = null;
69     private int activeRow = -1;
70     
71     ListSelectionListener groupsHighlightListener;
72     
73     public EntryTable(EntryTableModel tm_, BasePanel panel_, JabRefPreferences prefs_) {
74         super(tm_);
75         this.tableModel = tm_;
76         setBorder(null);
77         panel = panel_;
78         // Add the global focus listener, so a menu item can see if this table was focused when
79         // an action was called.
80         addFocusListener(Globals.focusListener);
81
82         // enable DnD
83         setDragEnabled(true);
84         // The following line is commented because EntryTableTransferHandler's
85         // constructor now only accepts MainTable which has replaced EntryTable.
86         // setTransferHandler(new EntryTableTransferHandler(this));
87
88   //renderingHints = g2.getRenderingHints();
89          //renderingHints.put(RenderingHints.KEY_ANTIALIASING,
90         //                 RenderingHints.VALUE_ANTIALIAS_ON);
91         //renderingHints.put(RenderingHints.KEY_RENDERING,
92         //                 RenderingHints.VALUE_RENDER_QUALITY);
93         prefs = prefs_;
94         //antialiasing =
95         //System.out.println(antialiasing);
96         ctrlClick = prefs.getBoolean("ctrlClick");
97         tableColorCodes = prefs.getBoolean("tableColorCodesOn");
98         getTableHeader().setReorderingAllowed(false); // To prevent color bugs. Must be fixed.
99         setGridColor(Globals.prefs.getColor("gridColor"));
100         setShowVerticalLines(true);
101         setShowHorizontalLines(true);
102         //setColumnSelectionAllowed(true);
103         setColumnSelectionAllowed(false);
104         setRowSelectionAllowed(true);
105         setAutoResizeMode(prefs.getInt("autoResizeMode"));
106         DefaultCellEditor dce = new DefaultCellEditor(new JTextField());
107         dce.setClickCountToStart(2);
108         setDefaultEditor(String.class, dce);
109         getTableHeader().addMouseListener(new MouseAdapter() {
110           public void mouseClicked(MouseEvent e)
111           {
112             int col = getTableHeader().columnAtPoint(e.getPoint());
113             if (col >= 1) { //tableModel.padleft) { // A valid column, but not the first.
114               String s = tableModel.getFieldName(col);
115               /*
116                * If the user adjusts the header size the sort event is
117                * always triggered.
118                * To avoid this behaviour we check if the mouse is
119                * inside the label's bounds and has a certain distance (offset)
120                * to the label border.
121                *
122                * Sascha Hunold <hunoldinho@users.sourceforge.net>
123                */
124
125               Point p = e.getPoint();
126               int colindex = getTableHeader().columnAtPoint(p);
127               if( colindex >= 0 ) {
128                   final int initoffset = 3;
129                   int xoffset = initoffset;
130                   for (int i = 0; i < colindex; i++) {
131                       xoffset += getColumnModel().getColumn(i).getWidth();
132                   }
133                   TableColumn column = getColumnModel().getColumn(col);
134                   int cw = column.getWidth();
135                   int ch = getTableHeader().getHeight();
136
137                   Rectangle r = new Rectangle();
138
139                   r.setBounds(xoffset, 0/*offset*/, cw-2*initoffset, ch/*-2*offset*/);
140
141                   if (!r.contains(p)) {
142                       return;
143                   }
144               }
145
146               if (!s.equals(prefs.get("priSort"))) {
147                 prefs.put("priSort", s);
148                   // Now, if the selected column is an icon column, set the sort to binary mode,
149                   // meaning that it only separates set fields from empty fields, and does no
150                   // internal sorting of set fields:
151                   if (tableModel.getIconTypeForColumn(col) == null)
152                       prefs.putBoolean("priBinary", false);
153                   else
154                       prefs.putBoolean("priBinary", true);
155               }
156                 // ... or change sort direction
157               else prefs.putBoolean("priDescending",
158                                     !prefs.getBoolean("priDescending"));
159               tableModel.remap();
160               
161             }
162           }
163         });
164
165         addMouseListener(new TableClickListener()); // Add the listener that responds to clicks on the table.
166
167         // Trying this to get better handling of the row selection stuff.
168         setSelectionModel(new javax.swing.DefaultListSelectionModel() {
169           public void setSelectionInterval(int index0, int index1) {
170             // Prompt user here
171             //Util.pr("Selection model: "+panel.entryEditorAllowsChange());
172             if (panel.entryEditorAllowsChange() == false) {
173               panel.moveFocusToEntryEditor();
174               return;
175             }
176             super.setSelectionInterval(index0, index1);
177           }
178         });
179
180         addSelectionListener(); // Add the listener that responds to new entry selection.
181
182         groupsHighlightListener = new ListSelectionListener() {
183             public void valueChanged(ListSelectionEvent e) {
184                 /*
185                 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
186                     panel.getGroupSelector().showMatchingGroups(
187                             panel.getSelectedEntries(), false);
188                 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
189                     panel.getGroupSelector().showMatchingGroups(
190                             panel.getSelectedEntries(), true);
191                 else // no highlight
192                     panel.getGroupSelector().showMatchingGroups(null, true);
193                     */
194             }
195         };
196         getSelectionModel().addListSelectionListener(groupsHighlightListener);
197         
198         // (to update entry editor or preview)
199         setWidths();
200         sp.getViewport().setBackground(Globals.prefs.getColor("tableBackground"));
201         updateFont();
202       }
203
204     /**
205      * Get the row number for the row that is active, in the sense that the preview or
206      * entry editor should show the corresponding entry.
207      * @return The active row number, or -1 if no row is active.
208      */
209     public int getActiveRow() {
210         return activeRow;
211     }
212
213
214     /**
215      * Get the active entry, in the sense that the preview or entry editor should
216      * show it.
217      * @return The active entry, or null if no row is active.
218      */
219
220     public BibtexEntry getActiveEntry() {
221         //System.out.println("EntryTable.getActiveEntry: "+activeRow);
222         return ((activeRow >= 0) && (activeRow < getRowCount())) ? tableModel.getEntryForRow(activeRow) : null;
223     }
224
225
226     /**
227      * Updates our Set containing the last row selection. Ckecks which rows were ADDED
228      * to the selection, to see what new entry should be previewed.
229      * Returns the number of the row that should be considered active, or -1 if none.
230      *
231      * This method may have some potential for optimization.
232      *
233      * @param rows
234      * @return
235      */
236     private int resolveNewSelection(int[] rows) {
237         HashSet newSel = new HashSet();
238         for (int i=0; i<rows.length; i++) {
239             Integer row = new Integer(rows[i]);
240             newSel.add(row);
241         }
242         // Store a clone of this Set:
243         HashSet tmp = new HashSet(newSel);
244         newSel.removeAll(lastSelection);
245         // Set the new selection as the last:
246         lastSelection = tmp;
247         // We return an appropriate row number if a single additional entry was selected:
248         int result = -1;
249         if (newSel.size()==1)
250             result = ((Integer)newSel.iterator().next()).intValue();
251
252         // .. or if the current selection is only one entry:
253         if ((result<0) && (rows.length == 1))
254             result = rows[0];
255         return result;
256     }
257
258       /**
259        * A ListSelectionListener for updating the preview panel when the user selects an
260        * entry. Should only be active when preview is enabled.
261        */
262       public void addSelectionListener() {
263         if (previewListener == null)
264           previewListener = new ListSelectionListener() {
265             public void valueChanged(final ListSelectionEvent e) {
266               if (!selectionListenerOn) return;
267               if (!e.getValueIsAdjusting()) {
268                   // We must use invokeLater() to postpone the updating. This is because of
269                   // the situation where an EntryEditor has changes in one of the FieldEditors
270                   // that need to be stored. This storage is initiated by a focusLost() call,
271                   // and results in a call to refreshTable() in BasePanel, which messes
272                   // with the table selection. After that chain has finished, the selection
273                   // will have been reset correctly, so we make sure everything is ok by
274                   // putting the updating based on table selection behind it in the event queue.
275                   SwingUtilities.invokeLater(new Thread() {
276                           public void run() {
277                               // If a single new row was selected, set it as the active row:
278                               activeRow = resolveNewSelection(getSelectedRows());
279
280                               if (getSelectedRowCount() == 1) {
281                                   //int row = getSelectedRow(); //e.getFirstIndex();
282                                   //if (row >= 0) {
283                                       // Update the value for which entry is shown:
284                                     //  activeRow = row;
285
286                                     //panel.updateViewToSelected();
287                                     // guarantee that the the entry is visible
288                                     ensureVisible(activeRow);
289
290                               } else {
291                                   /* With a multiple selection, there are three alternative behaviours:
292                                      1. Disable the entry editor. Do not update it.
293                                      2. Do not disable the entry editor, and do not update it.
294                                      3. Update the entry editor, and keep it enabled.
295
296                                      We currently implement 1 and 2, and choose between them based on
297                                      prefs.getBoolean("disableOnMultipleSelection");
298                                   */
299                                   if (prefs.getBoolean("disableOnMultipleSelection")) { // 1.
300                                       panel.setEntryEditorEnabled(false);
301                                   }
302                                   // We want the entry preview to update when the user expands the
303                                   // selection one entry at a time:
304                                   //if ((e.getLastIndex()-e.getFirstIndex()) <= 1) {
305                                   //if (activeRow >= 0)
306                                     //panel.updateViewToSelected();
307                                   //}
308                                   // 2. Do nothing.
309                               }
310
311                               if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
312                                 panel.getGroupSelector().showMatchingGroups(
313                                     panel.getSelectedEntries(), false);
314                             else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
315                                 panel.getGroupSelector().showMatchingGroups(
316                                     panel.getSelectedEntries(), true);
317                             else // no highlight
318                                 panel.getGroupSelector().showMatchingGroups(null, true);
319                           }
320                       });
321               }
322             }
323               };
324         getSelectionModel().addListSelectionListener(previewListener);
325       }
326
327       /**
328        * Remove the preview listener.
329        */
330       public void disablePreviewListener() {
331         getSelectionModel().removeListSelectionListener(previewListener);
332       }
333
334       /**
335        * This method overrides the superclass' to disable the selection listener while the
336        * row selection is adjusted.
337        */
338       public void setRowSelectionInterval(int row1, int row2) {
339         boolean oldState = selectionListenerOn;
340         selectionListenerOn = false;
341         // Introducing a try-catch here to maybe track down the preview update bug
342         // that occurs sometimes (20050405 M. Alver):
343         try {
344             super.setRowSelectionInterval(row1, row2);
345             activeRow = resolveNewSelection(getSelectedRows());
346             selectionListenerOn = oldState;
347         } catch (IllegalArgumentException ex) {
348             ex.printStackTrace();
349             System.out.println("Error occured. Trying to recover...");
350             // Maybe try to remap the entry table:
351             tableModel.remap();
352             clearSelection();
353             selectionListenerOn = oldState;
354         }
355       }
356
357       public void addRowSelectionIntervalQuietly(int row1, int row2) {
358           boolean oldState = selectionListenerOn;
359           selectionListenerOn = false;
360           //if (row2 < getModel().getRowCount()) {
361           try {
362             super.addRowSelectionInterval(row1, row2);
363             selectionListenerOn = oldState;
364           } catch (IllegalArgumentException ex) {
365               ex.printStackTrace();
366               System.out.println("Error occured. Trying to recover...");
367             // Maybe try to remap the entry table:
368             tableModel.remap();
369             clearSelection();
370               selectionListenerOn = oldState;
371           }
372
373       }
374
375     /*public boolean surrendersFocusOnKeystroke() {
376         return true;
377         }*/
378
379       /**
380        * This method overrides the superclass' to disable the selection listener while the
381        * selection is cleared.
382        */
383       public void clearSelection() {
384         boolean oldState = selectionListenerOn;
385         selectionListenerOn = false;
386         super.clearSelection();
387         selectionListenerOn = oldState;
388       }
389
390       /**
391        * Enables or disables the selectionlistener. Useful if the selection needs to be
392        * updated in several steps, without the table responding between each.
393        * @param enabled boolean
394        */
395       public void setSelectionListenerEnabled(boolean enabled) {
396         selectionListenerOn = enabled;
397       }
398
399       /**
400        * Turns off any cell editing going on.
401        */
402       protected void assureNotEditing() {
403         if (isEditing()) {
404           int col = getEditingColumn(),
405               row = getEditingRow();
406           getCellEditor(row, col).stopCellEditing();
407         }
408       }
409
410
411     public void setWidths() {
412         // Setting column widths:
413         int ncWidth = prefs.getInt("numberColWidth");
414         String[] widths = prefs.getStringArray("columnWidths");
415         TableColumnModel cm = getColumnModel();
416         cm.getColumn(0).setPreferredWidth(ncWidth);
417         for (int i=1; i<tableModel.padleft; i++) {
418           // Lock the width of icon columns.
419           cm.getColumn(i).setPreferredWidth(GUIGlobals.WIDTH_ICON_COL);
420           cm.getColumn(i).setMinWidth(GUIGlobals.WIDTH_ICON_COL);
421           cm.getColumn(i).setMaxWidth(GUIGlobals.WIDTH_ICON_COL);
422         }
423         for (int i=tableModel.padleft; i<getModel().getColumnCount(); i++) {
424             try {
425                 cm.getColumn(i).setPreferredWidth(Integer.parseInt(widths[i-tableModel.padleft]));
426             } catch (Throwable ex) {
427                 Globals.logger("Exception while setting column widths. Choosing default.");
428                 cm.getColumn(i).setPreferredWidth(GUIGlobals.DEFAULT_FIELD_LENGTH);
429             }
430             //cm.getColumn(i).setPreferredWidth(GUIGlobals.getPreferredFieldLength(getModel().getColumnName(i)));
431         }
432     }
433
434     public JScrollPane getPane() {
435                 return sp;
436     }
437
438     /*public void setShowingSearchResults(boolean search,
439                                         boolean group) {
440         showingSearchResults = search;
441         showingGroup = group;
442     }
443 */
444     public void setRightClickMenu(JPopupMenu rcm) {
445         rightClickMenu = rcm;
446     }
447
448   /**
449    * This class handles clicks on the EntryTable that should trigger specific
450    * events, like opening an entry editor, the context menu or a pdf file.
451    */
452   class TableClickListener extends MouseAdapter {
453       public void mouseReleased(MouseEvent e) {
454           // First find the column on which the user has clicked.
455           final int col = columnAtPoint(e.getPoint()),
456               row = rowAtPoint(e.getPoint());
457           // Check if the user has right-clicked. If so, open the right-click menu.
458           if (e.isPopupTrigger()) {
459             processPopupTrigger(e, row, col);
460             return;
461           }
462       }
463       protected void processPopupTrigger(MouseEvent e, int row, int col) {
464           int selRow = getSelectedRow();
465           if (selRow == -1 ||// (getSelectedRowCount() == 0))
466                   !isRowSelected(rowAtPoint(e.getPoint()))) {
467             setRowSelectionInterval(row, row);
468             //panel.updateViewToSelected();
469           }
470           rightClickMenu = new RightClickMenu(panel, panel.metaData);
471           rightClickMenu.show(EntryTable.this, e.getX(), e.getY());
472       }
473       public void mousePressed(MouseEvent e) {
474
475         // First find the column on which the user has clicked.
476         final int col = columnAtPoint(e.getPoint()),
477             row = rowAtPoint(e.getPoint());
478
479
480         // A double click on an entry should open the entry's editor.
481         if (/*(col == 0)*/!isCellEditable(row, col) && (e.getClickCount() == 2)) {
482           try{ panel.runCommand("edit");
483               return;
484               /*showEntry(be);
485
486                     if (splitPane.getBottomComponent() != null) {
487                         new FocusRequester(splitPane.getBottomComponent());
488                     }                                                      */
489           } catch (Throwable ex) {
490             ex.printStackTrace();
491           }
492         }
493
494         // Check if the user has right-clicked. If so, open the right-click menu.
495         if (e.isPopupTrigger()) {
496           processPopupTrigger(e, row, col);
497           return;
498         }
499
500         // Check if the user has clicked on an icon cell to open url or pdf.
501         if (tableModel.getCellStatus(0, col) == EntryTableModel.ICON_COL) {
502
503           // Get the row number also:
504           Object value = getValueAt(row, col);
505           if (value == null) return; // No icon here, so we do nothing.
506           /*Util.pr("eouaeou");
507           JButton button = (JButton)value;
508
509           MouseEvent buttonEvent =
510               (MouseEvent)SwingUtilities.convertMouseEvent(ths, e, button);
511           button.dispatchEvent(buttonEvent);
512           // This is necessary so that when a button is pressed and released
513           // it gets rendered properly.  Otherwise, the button may still appear
514           // pressed down when it has been released.
515           ths.repaint();
516
517           */
518
519
520
521           // Get the icon type. Corresponds to the field name.
522           final String[] iconType = tableModel.getIconTypeForColumn(col);
523           int hasField = -1;
524           for (int i=iconType.length-1; i>= 0; i--)
525             if (tableModel.hasField(row, iconType[i]))
526               hasField = i;
527           if (hasField == -1)
528             return;
529           final String fieldName = iconType[hasField];
530
531           // Open it now. We do this in a thread, so the program won't freeze during the wait.
532           (new Thread() {
533             public void run() {
534               panel.output(Globals.lang("External viewer called") + ".");
535               BibtexEntry be = panel.database().getEntryById(tableModel.
536                       getIdForRow(row));
537               if (be == null) {
538                 Globals.logger("Error: could not find entry.");
539                 return;
540               }
541
542               Object link = be.getField(fieldName);
543               if (iconType == null) {
544                 Globals.logger("Error: no link to " + fieldName + ".");
545                 return; // There is an icon, but the field is not set.
546               }
547
548               try {
549                 Util.openExternalViewer( (String) link, fieldName, prefs);
550               }
551               catch (IOException ex) {
552                 panel.output(Globals.lang("Error")+": "+ex.getMessage());
553               }
554             }
555
556           }).start();
557         }
558       }
559     }
560
561     public TableCellRenderer getCellRenderer(int row, int column) {
562
563         // This method asks the table model whether the given cell represents a
564         // required or optional field, and returns the appropriate renderer.
565         int score = -3;
566         TableCellRenderer renderer;
567
568         int status;
569         try { // This try clause is here to contain a bug.
570           status = tableModel.getCellStatus(row, column);
571         } catch (ArrayIndexOutOfBoundsException ex) {
572             Globals.logger("Error happened in getCellRenderer method of EntryTable, for cell ("+row+","+column+").");
573             return defRenderer; // This should not occur.
574         }
575
576
577         if (!panel.coloringBySearchResults ||
578             tableModel.nonZeroField(row, Globals.SEARCH))
579             score++;
580         if (!panel.coloringByGroup ||
581             tableModel.nonZeroField(row, Globals.GROUPSEARCH))
582             score+=2;
583
584         // Now, a grayed out renderer is for entries with -1, and
585         // a very grayed out one for entries with -2
586         if (score < -1)
587             renderer = veryGrayedOutRenderer;
588         else if (score == -1)
589             renderer = grayedOutRenderer;
590
591         else if (!tableColorCodes)
592             renderer = defRenderer;
593         else if (column == 0) {
594             // Return a renderer with red background if the entry is incomplete.
595             renderer = defRenderer;
596             if (tableModel.isComplete(row))
597                 renderer = defRenderer;
598             else {
599               //if (tableModel.hasCrossRef(row))
600               //  renderer = maybeIncRenderer;
601               //else
602               renderer = incRenderer;//incompleteEntryRenderer;
603             }
604
605             //return (tableModel.isComplete(row) ? defRenderer: incRenderer);
606         }
607         else if (status == EntryTableModel.REQUIRED)
608             renderer = reqRenderer;
609         else if (status == EntryTableModel.OPTIONAL)
610             renderer = optRenderer;
611         else if (status == EntryTableModel.BOOLEAN)
612           renderer = getDefaultRenderer(Boolean.class);
613         else renderer = defRenderer;
614         //Util.pr("("+row+","+column+"). "+status+" "+renderer.toString());
615
616         // For MARKED feature:
617         if (tableModel.isMarked(row) && (renderer != incRenderer)) {
618           return markedRenderer;
619         }
620
621         return renderer;
622
623         /*
624         int test = row - 4*(row/4);
625         if (test <= 1)
626             return renderer;
627         else {
628             return renderer.darker();
629             }*/
630     }
631
632     public void scrollTo(int y) {
633         JScrollBar scb = sp.getVerticalScrollBar();
634         scb.setValue(y*scb.getUnitIncrement(1));
635     }
636
637     public BibtexEntry[] getSelectedEntries() {
638         BibtexEntry[] bes = null;
639         int[] rows = getSelectedRows();
640         //int[] cols = getSelectedColumns();
641
642         // Entries are selected if only the first or multiple
643         // columns are selected.
644         //if (((cols.length == 1) && (cols[0] == 0)) ||
645         //(cols.length > 1)) { // entryTable.getColumnCount())) {
646         if (rows.length > 0) {
647             bes = new BibtexEntry[rows.length];
648             for (int i=0; i<rows.length; i++) {
649                 bes[i] = tableModel.db.getEntryById(tableModel.getIdForRow(rows[i]));
650             }
651         }
652         return bes;
653     }
654
655
656     // The following classes define the renderers used to render required
657     // and optional fields in the table. The purpose of these renderers is
658     // to visualize which fields are needed for each entry.
659    private GeneralRenderer defRenderer = new GeneralRenderer(Globals.prefs.getColor("tableBackground"),
660             Globals.prefs.getColor("tableText"), antialiasing),
661         reqRenderer = new GeneralRenderer(Globals.prefs.getColor("tableReqFieldBackground"), Globals.prefs.getColor("tableText"), antialiasing),
662         optRenderer = new GeneralRenderer(Globals.prefs.getColor("tableOptFieldBackground"), Globals.prefs.getColor("tableText"), antialiasing),
663         incRenderer = new IncompleteRenderer(this, antialiasing),
664             //new Renderer(GUIGlobals.tableIncompleteEntryBackground),
665             //Globals.lang("This entry is incomplete")),
666         grayedOutRenderer = new GeneralRenderer(Globals.prefs.getColor("grayedOutBackground"),
667                                          Globals.prefs.getColor("grayedOutText"), antialiasing),
668         veryGrayedOutRenderer = new GeneralRenderer(Globals.prefs.getColor("veryGrayedOutBackground"),
669                                              Globals.prefs.getColor("veryGrayedOutText"), antialiasing),
670         markedRenderer = new GeneralRenderer(Globals.prefs.getColor("markedEntryBackground"),
671                 Globals.prefs.getColor("tableText"), antialiasing);
672
673     class IncompleteRenderer extends GeneralRenderer {
674         public IncompleteRenderer(JTable table, boolean antialiasing) {
675             super(Globals.prefs.getColor("incompleteEntryBackground"), antialiasing);
676         }
677         protected void setValue(Object value) {
678             super.setValue(value);
679             super.setToolTipText(Globals.lang("This entry is incomplete"));
680         }
681     }
682
683     /* public TableCellRenderer iconRenderer = new IconCellRenderer();
684         //new JTableButtonRenderer(getDefaultRenderer(JButton.class));
685     class IconCellRenderer extends DefaultTableCellRenderer {
686         protected void setValue(Object value) {
687             if (value instanceof Icon) {
688                 setIcon((Icon)value);
689                 super.setValue(null);
690             } else {
691                 setIcon(null);
692                 super.setValue(value);
693             }
694         }
695     }
696
697
698    class JTableButtonRenderer implements TableCellRenderer {
699       private TableCellRenderer __defaultRenderer;
700
701       public JTableButtonRenderer(TableCellRenderer renderer) {
702         __defaultRenderer = renderer;
703       }
704
705       public Component getTableCellRendererComponent(JTable table, Object value,
706                                                      boolean isSelected,
707                                                      boolean hasFocus,
708                                                      int row, int column)
709       {
710         if(value instanceof Component)
711           return (Component)value;
712         return __defaultRenderer.getTableCellRendererComponent(
713       table, value, isSelected, hasFocus, row, column);
714       }
715     }*/
716
717
718     public void ensureVisible(int row) {
719         JScrollBar vert = sp.getVerticalScrollBar();
720         int y = row*getRowHeight();
721         if ((y < vert.getValue()) || (y > vert.getValue()+vert.getVisibleAmount()))
722             scrollToCenter(row, 1);
723     }
724
725         public void scrollToCenter( int rowIndex, int vColIndex) {
726         if (!(this.getParent() instanceof JViewport)) {
727             return;
728         }
729
730         JViewport viewport = (JViewport)this.getParent();
731
732         // This rectangle is relative to the table where the
733         // northwest corner of cell (0,0) is always (0,0).
734         Rectangle rect = this.getCellRect(rowIndex, vColIndex, true);
735
736         // The location of the view relative to the table
737         Rectangle viewRect = viewport.getViewRect();
738
739          // Translate the cell location so that it is relative
740         // to the view, assuming the northwest corner of the
741         // view is (0,0).
742         rect.setLocation(rect.x-viewRect.x, rect.y-viewRect.y);
743
744         // Calculate location of rect if it were at the center of view
745         int centerX = (viewRect.width-rect.width)/2;
746         int centerY = (viewRect.height-rect.height)/2;
747
748         // Fake the location of the cell so that scrollRectToVisible
749         // will move the cell to the center
750         if (rect.x < centerX) {
751             centerX = -centerX;
752         }
753         if (rect.y < centerY) {
754             centerY = -centerY;
755         }
756         rect.translate(centerX, centerY);
757
758         // Scroll the area into view.
759         viewport.scrollRectToVisible(rect);
760
761         revalidate();
762         repaint();
763     }
764
765   /**
766    * updateFont
767    */
768   public void updateFont() {
769       setFont(GUIGlobals.CURRENTFONT);
770       setRowHeight(GUIGlobals.TABLE_ROW_PADDING+GUIGlobals.CURRENTFONT.getSize());
771   }
772
773   public void updateUI() {
774       super.updateUI();
775       setUI(new CustomTableUI());
776   }
777
778
779
780   class CustomTableUI extends BasicTableUI {
781     public void installUI(JComponent c) {
782       super.installUI(c);
783       c.remove(rendererPane);
784       rendererPane = new CustomCellRendererPane();
785       c.add(rendererPane);
786     }
787
788     /**
789      * Overrides paintComponent to NOT clone the Graphics
790      * passed in and NOT validate the Component passed in.
791      * This is done for performance reasons.
792      */
793     private class CustomCellRendererPane extends CellRendererPane {
794         private Rectangle tmpRect = new Rectangle();
795
796         public void repaint() {
797         }
798
799         public void repaint(int x, int y, int width, int height) {
800         }
801
802         public void paintComponent(Graphics g, Component c, Container p,
803                                    int x, int y, int w, int h,
804                                    boolean shouldValidate) {
805           if (c == null) {
806             if (p != null) {
807               Color oldColor = g.getColor();
808               g.setColor(p.getBackground());
809               g.fillRect(x, y, w, h);
810               g.setColor(oldColor);
811             }
812             return;
813           }
814           if (c.getParent() != this) {
815             this.add(c);
816           }
817
818           c.setBounds(x, y, w, h);
819
820           boolean wasDoubleBuffered = false;
821           JComponent jc = (c instanceof JComponent) ? (JComponent)c : null;
822           if (jc != null && jc.isDoubleBuffered()) {
823             wasDoubleBuffered = true;
824             jc.setDoubleBuffered(false);
825           }
826
827           // Don't create a new Graphics, reset the clip and translate
828           // the origin.
829           Rectangle clip = g.getClipBounds(tmpRect);
830           g.clipRect(x, y, w, h);
831           g.translate(x, y);
832           c.paint(g);
833           g.translate(-x, -y);
834           g.setClip(clip.x, clip.y, clip.width, clip.height);
835           if (wasDoubleBuffered) {
836             jc.setDoubleBuffered(true);
837           }
838           c.setBounds(-w, -h, 0, 0);
839         }
840       }
841
842     }
843
844 }
845