001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the file COPYING.                     *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * If you do not have access to this file, you may request a copy from       *
011 * help@hdfgroup.org.                                                        *
012 ****************************************************************************/
013
014package hdf.view;
015
016import java.awt.Color;
017import java.awt.Component;
018import java.awt.event.ActionEvent;
019import java.awt.event.ActionListener;
020import java.awt.event.KeyEvent;
021import java.awt.event.KeyListener;
022import java.awt.event.MouseEvent;
023import java.io.BufferedInputStream;
024import java.io.BufferedWriter;
025import java.io.File;
026import java.io.FileWriter;
027import java.io.InputStream;
028import java.io.PrintWriter;
029import java.io.RandomAccessFile;
030import java.util.Enumeration;
031import java.util.HashMap;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Map;
035
036import javax.print.Doc;
037import javax.print.DocFlavor;
038import javax.print.DocPrintJob;
039import javax.print.PrintService;
040import javax.print.PrintServiceLookup;
041import javax.print.SimpleDoc;
042import javax.print.StreamPrintServiceFactory;
043import javax.swing.CellEditor;
044import javax.swing.DefaultCellEditor;
045import javax.swing.JFileChooser;
046import javax.swing.JFrame;
047import javax.swing.JInternalFrame;
048import javax.swing.JLabel;
049import javax.swing.JMenu;
050import javax.swing.JMenuBar;
051import javax.swing.JMenuItem;
052import javax.swing.JOptionPane;
053import javax.swing.JPanel;
054import javax.swing.JScrollPane;
055import javax.swing.JTable;
056import javax.swing.JTextArea;
057import javax.swing.JTextField;
058import javax.swing.JViewport;
059import javax.swing.SwingConstants;
060import javax.swing.UIManager;
061import javax.swing.WindowConstants;
062import javax.swing.event.ChangeEvent;
063import javax.swing.event.ListSelectionEvent;
064import javax.swing.table.AbstractTableModel;
065import javax.swing.table.DefaultTableCellRenderer;
066import javax.swing.table.JTableHeader;
067import javax.swing.table.TableCellRenderer;
068import javax.swing.table.TableColumn;
069import javax.swing.table.TableColumnModel;
070
071import hdf.object.Dataset;
072import hdf.object.FileFormat;
073import hdf.object.HObject;
074import hdf.object.ScalarDS;
075
076/**
077 * TextView displays an HDF string dataset in text.
078 *
079 * @author Peter X. Cao
080 * @version 2.4 9/6/2007
081 */
082public class DefaultTextView extends JInternalFrame implements TextView,
083        ActionListener, KeyListener {
084    private static final long serialVersionUID = 3892752752951438428L;
085
086    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultTextView.class);
087
088    /**
089     * The main HDFView.
090     */
091    private final ViewManager viewer;
092
093    /**
094     * The Scalar Dataset.
095     */
096    private ScalarDS                dataset;
097
098    /**
099     * The string text.
100     */
101    private String[]                text;
102
103    /** The table to display the text content */
104    private JTable                  table;
105
106    private boolean                 isReadOnly = false;
107
108    private boolean                 isTextChanged = false;
109
110    private RowHeader               rowHeaders = null;
111
112    private int                     indexBase = 0;
113
114    private TextAreaEditor          textEditor = null;
115
116    public DefaultTextView(ViewManager theView) {
117        this(theView, null);
118    }
119
120    /**
121     * Constructs a TextView.
122     *
123     * @param theView
124     *            the main HDFView.
125     * @param map
126     *            the properties on how to show the data. The map is used to
127     *            allow applications to pass properties on how to display the
128     *            data, such as, transposing data, showing data as character,
129     *            applying bitmask, and etc. Predefined keys are listed at
130     *            ViewProperties.DATA_VIEW_KEY.
131     */
132    public DefaultTextView(ViewManager theView, HashMap map) {
133        viewer = theView;
134
135        text = null;
136        table = null;
137        dataset = null;
138        textEditor = new TextAreaEditor(this);
139
140        if (ViewProperties.isIndexBase1())
141            indexBase = 1;
142
143        HObject hobject = null;
144        if (map != null)
145            hobject = (HObject) map.get(ViewProperties.DATA_VIEW_KEY.OBJECT);
146        else
147            hobject = theView.getTreeView().getCurrentObject();
148
149        if (!(hobject instanceof ScalarDS)) {
150            return;
151        }
152
153        dataset = (ScalarDS) hobject;
154
155        if (!dataset.isText()) {
156            viewer.showStatus("Cannot display non-text dataset in text view.");
157            dataset = null;
158            return;
159        }
160
161        isReadOnly = dataset.getFileFormat().isReadOnly();
162
163        try {
164            text = (String[]) dataset.getData();
165        }
166        catch (Exception ex) {
167            JOptionPane.showMessageDialog(this, ex.getMessage(),
168                    "TextView:"+getTitle(),
169                    JOptionPane.ERROR_MESSAGE);
170            text = null;
171        }
172
173        if (text == null) {
174            viewer.showStatus("Loading text dataset failed - "
175                    + dataset.getName());
176            dataset = null;
177            return;
178        }
179
180        String fname = new java.io.File(dataset.getFile()).getName();
181        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
182        this.setTitle("TextView  -  " + dataset.getName() + "  -  "
183                + dataset.getPath() + "  -  " + fname);
184        this.setFrameIcon(ViewProperties.getTextIcon());
185        this.setName("textdata");
186
187        int rank = dataset.getRank();
188        long start[] = dataset.getStartDims();
189        long count[] = dataset.getSelectedDims();
190
191        String colName = "Data selection:   ["+start[0];
192        for (int i=1; i<rank; i++) {
193            colName += ", "+start[i];
194        }
195        colName += "] ~ ["+(start[0]+count[0]-1);
196        for (int i=1; i<rank; i++) {
197            colName += ", "+(start[i]+count[i]-1);
198        }
199        colName += "]";
200
201        table = createTable(colName);
202
203        JTableHeader colHeader = table.getTableHeader();
204        colHeader.setReorderingAllowed(false);
205        //colHeader.setBackground(Color.black);
206
207        rowHeaders = new RowHeader(table, dataset);
208
209        // add the table to a scroller
210        JScrollPane scrollingTable = new JScrollPane(table);
211        scrollingTable.getVerticalScrollBar().setUnitIncrement(100);
212        scrollingTable.getHorizontalScrollBar().setUnitIncrement(100);
213
214        JViewport viewp = new JViewport();
215        viewp.add(rowHeaders);
216        viewp.setPreferredSize(rowHeaders.getPreferredSize());
217        scrollingTable.setRowHeader(viewp);
218
219        TableColumnModel cmodel = table.getColumnModel();
220        TextAreaRenderer textAreaRenderer = new TextAreaRenderer();
221
222        cmodel.getColumn(0).setCellRenderer(textAreaRenderer);
223        cmodel.getColumn(0).setCellEditor(textEditor);
224
225        ((JPanel) getContentPane()).add(scrollingTable);
226
227        setJMenuBar(createMenuBar());
228    }
229
230    public void actionPerformed(ActionEvent e) {
231        Object source = e.getSource();
232        String cmd = e.getActionCommand();
233
234        if (cmd.equals("Close")) {
235            dispose(); // terminate the application
236        }
237        else if (cmd.equals("Save to text file")) {
238            try {
239                saveAsText();
240            }
241            catch (Exception ex) {
242                JOptionPane.showMessageDialog((JFrame) viewer, ex, getTitle(),
243                        JOptionPane.ERROR_MESSAGE);
244            }
245        }
246        else if (cmd.equals("Save changes")) {
247            updateValueInFile();
248        }
249        else if (cmd.equals("Print")) {
250            print();
251        }
252    }
253
254    /**
255     * Creates a Table to hold a compound dataset.
256     *
257     * @param colName the name of the column
258     */
259    private JTable createTable(final String colName) {
260        JTable theTable = null;
261
262        AbstractTableModel tm =  new AbstractTableModel()
263        {
264            public int getColumnCount() {
265                return 1;
266            }
267
268            public int getRowCount() {
269                return text.length;
270            }
271
272            public String getColumnName(int col) {
273                return colName;
274            }
275
276            public Object getValueAt(int row, int column)
277            {
278                return text[row];
279            }
280        };
281
282        theTable = new JTable(tm) {
283            private static final long serialVersionUID = -6571266777012522255L;
284
285            @Override
286            public boolean isCellEditable(int row, int column) {
287                return !isReadOnly;
288            }
289
290            @Override
291            public void editingStopped(ChangeEvent e) {
292                int row = getEditingRow();
293                int col = getEditingColumn();
294                super.editingStopped(e);
295
296                Object source = e.getSource();
297
298                if (source instanceof CellEditor) {
299                    CellEditor editor = (CellEditor) source;
300                    String cellValue = (String) editor.getCellEditorValue();
301                    text[row] = cellValue;
302                } // if (source instanceof CellEditor)
303            }
304        };
305        theTable.setName("TextView");
306
307        return theTable;
308    }
309
310    public void keyPressed(KeyEvent e) {
311    }
312
313    public void keyReleased(KeyEvent e) {
314    }
315
316    public void keyTyped(KeyEvent e) {
317        isTextChanged = true;
318    }
319
320    private JMenuBar createMenuBar() {
321        JMenuBar bar = new JMenuBar();
322        JMenu menu = new JMenu("Text", false);
323        menu.setMnemonic('T');
324        bar.add(menu);
325
326        JMenuItem item = new JMenuItem("Save To Text File");
327        // item.setMnemonic(KeyEvent.VK_T);
328        item.addActionListener(this);
329        item.setActionCommand("Save to text file");
330        menu.add(item);
331
332        menu.addSeparator();
333
334        item = new JMenuItem("Save Changes");
335        item.addActionListener(this);
336        item.setActionCommand("Save changes");
337        menu.add(item);
338
339        menu.addSeparator();
340
341        menu.addSeparator();
342
343        item = new JMenuItem("Close");
344        item.addActionListener(this);
345        item.setActionCommand("Close");
346        menu.add(item);
347
348        return bar;
349    }
350
351    /**
352     * Update dataset value in file. The change will go to file.
353     */
354    public void updateValueInFile() {
355        if (!(dataset instanceof ScalarDS) || isReadOnly || !isTextChanged) return;
356
357        int row = table.getEditingRow();
358        if (row >= 0) {
359            // make sure to update the current row
360            String cellValue = (String) textEditor.getCellEditorValue();
361            text[row] = cellValue;
362        }
363
364        try {
365            dataset.write();
366        }
367        catch (Exception ex) {
368            JOptionPane.showMessageDialog(this, ex.getMessage(), getTitle(), JOptionPane.ERROR_MESSAGE);
369            return;
370        }
371        isTextChanged = false;
372    }
373
374    /** Save data as text. */
375    private void saveAsText() throws Exception {
376        final JFileChooser fchooser = new JFileChooser(dataset.getFile());
377        fchooser.setFileFilter(DefaultFileFilter.getFileFilterText());
378        fchooser.changeToParentDirectory();
379        fchooser.setDialogTitle("Save Current Data To Text File --- " + dataset.getName());
380
381        File chosenFile = new File(dataset.getName() + ".txt");
382        fchooser.setSelectedFile(chosenFile);
383        int returnVal = fchooser.showSaveDialog(this);
384
385        if (returnVal != JFileChooser.APPROVE_OPTION) {
386            return;
387        }
388
389        chosenFile = fchooser.getSelectedFile();
390        if (chosenFile == null) {
391            return;
392        }
393
394        String fname = chosenFile.getAbsolutePath();
395
396        // check if the file is in use
397        List fileList = viewer.getTreeView().getCurrentFiles();
398        if (fileList != null) {
399            FileFormat theFile = null;
400            Iterator iterator = fileList.iterator();
401            while (iterator.hasNext()) {
402                theFile = (FileFormat) iterator.next();
403                if (theFile.getFilePath().equals(fname)) {
404                    JOptionPane.showMessageDialog(this, "Unable to save data to file \"" + fname
405                                    + "\". \nThe file is being used.",
406                            getTitle(), JOptionPane.ERROR_MESSAGE);
407                    return;
408                }
409            }
410        }
411
412        if (chosenFile.exists()) {
413            int newFileFlag = JOptionPane.showConfirmDialog(this, "File exists. Do you want to replace it ?",
414                    this.getTitle(), JOptionPane.YES_NO_OPTION);
415            if (newFileFlag == JOptionPane.NO_OPTION) {
416                return;
417            }
418        }
419
420        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
421                chosenFile)));
422
423        int rows = text.length;
424        for (int i = 0; i < rows; i++) {
425            out.print(text[i].trim());
426            out.println();
427            out.println();
428        }
429
430        out.flush();
431        out.close();
432
433        viewer.showStatus("Data save to: " + fname);
434
435        try {
436            RandomAccessFile rf = new RandomAccessFile(chosenFile, "r");
437            long size = rf.length();
438            rf.close();
439            viewer.showStatus("File size (bytes): " + size);
440        }
441        catch (Exception ex) {
442            log.debug("raf file size:", ex);
443        }
444    }
445
446    @Override
447    public void dispose() {
448        if (isTextChanged && !isReadOnly) {
449            int op = JOptionPane.showConfirmDialog(this, "\""
450                    + dataset.getName() + "\" has changed.\n"
451                    + "Do you want to save the changes?", getTitle(),
452                    JOptionPane.YES_NO_OPTION);
453
454            if (op == JOptionPane.YES_OPTION) {
455                updateValueInFile();
456            }
457        }
458
459        viewer.removeDataView(this);
460
461        super.dispose();
462    }
463
464    // Implementing DataView.
465    public HObject getDataObject() {
466        return dataset;
467    }
468
469    // Implementing TextView.
470    public String[] getText() {
471        return text;
472    }
473
474    // print the table
475    private void print() {
476        StreamPrintServiceFactory[] spsf = StreamPrintServiceFactory
477                .lookupStreamPrintServiceFactories(null, null);
478        for (int i = 0; i < spsf.length; i++) {
479            System.out.println(spsf[i]);
480        }
481        DocFlavor[] docFlavors = spsf[0].getSupportedDocFlavors();
482        for (int i = 0; i < docFlavors.length; i++) {
483            System.out.println(docFlavors[i]);
484        }
485
486        // TODO: windows url
487        // Get a text DocFlavor
488        InputStream is = null;
489        try {
490            is = new BufferedInputStream(new java.io.FileInputStream(
491                    "e:\\temp\\t.html"));
492        }
493        catch (Exception ex) {
494            log.debug("Get a text DocFlavor:", ex);
495        }
496        DocFlavor flavor = DocFlavor.STRING.TEXT_HTML;
497
498        // Get all available print services
499        PrintService[] services = PrintServiceLookup.lookupPrintServices(null,
500                null);
501
502        // Print this job on the first print server
503        DocPrintJob job = services[0].createPrintJob();
504        Doc doc = new SimpleDoc(is, flavor, null);
505
506        // Print it
507        try {
508            job.print(doc, null);
509        }
510        catch (Exception ex) {
511            log.debug("print(): failure: ", ex);
512        }
513    }
514
515    private class TextAreaRenderer extends JTextArea implements
516            TableCellRenderer {
517        private static final long serialVersionUID = -5869975162678521978L;
518
519        private final DefaultTableCellRenderer adaptee = new DefaultTableCellRenderer();
520
521        /** map from table to map of rows to map of column heights */
522        private final Map cellSizes = new HashMap();
523
524        public TextAreaRenderer() {
525            setLineWrap(true);
526            setWrapStyleWord(true);
527        }
528
529        public Component getTableCellRendererComponent(
530                //
531                JTable table, Object obj, boolean isSelected, boolean hasFocus,
532                int row, int column) {
533            // set the colours, etc. using the standard for that platform
534            adaptee.getTableCellRendererComponent(table, obj, isSelected,
535                    hasFocus, row, column);
536            setForeground(adaptee.getForeground());
537            setBackground(adaptee.getBackground());
538            setBorder(adaptee.getBorder());
539            setFont(adaptee.getFont());
540            setText(adaptee.getText());
541
542            // This line was very important to get it working with JDK1.4
543            TableColumnModel columnModel = table.getColumnModel();
544            setSize(columnModel.getColumn(column).getWidth(), 100000);
545            int height_wanted = (int) getPreferredSize().getHeight();
546            addSize(table, row, column, height_wanted);
547            height_wanted = findTotalMaximumRowSize(table, row);
548            if (height_wanted != table.getRowHeight(row)) {
549                table.setRowHeight(row, height_wanted);
550                rowHeaders.setRowHeight(row, height_wanted);
551
552            }
553            return this;
554        }
555
556        private void addSize(JTable table, int row, int column, int height) {
557            Map rows = (Map) cellSizes.get(table);
558            if (rows == null) {
559                cellSizes.put(table, rows = new HashMap());
560            }
561            Map rowheights = (Map) rows.get(new Integer(row));
562            if (rowheights == null) {
563                rows.put(new Integer(row), rowheights = new HashMap());
564            }
565            rowheights.put(new Integer(column), new Integer(height));
566        }
567
568        /**
569         * Look through all columns and get the renderer. If it is also a
570         * TextAreaRenderer, we look at the maximum height in its hash table for
571         * this row.
572         */
573        private int findTotalMaximumRowSize(JTable table, int row) {
574            int maximum_height = 0;
575            Enumeration columns = table.getColumnModel().getColumns();
576            while (columns.hasMoreElements()) {
577                TableColumn tc = (TableColumn) columns.nextElement();
578                TableCellRenderer cellRenderer = tc.getCellRenderer();
579                if (cellRenderer instanceof TextAreaRenderer) {
580                    TextAreaRenderer tar = (TextAreaRenderer) cellRenderer;
581                    maximum_height = Math.max(maximum_height, tar
582                            .findMaximumRowSize(table, row));
583                }
584            }
585            return maximum_height;
586        }
587
588        private int findMaximumRowSize(JTable table, int row) {
589            Map rows = (Map) cellSizes.get(table);
590            if (rows == null) {
591                return 0;
592            }
593            Map rowheights = (Map) rows.get(new Integer(row));
594            if (rowheights == null) {
595                return 0;
596            }
597            int maximum_height = 0;
598            for (Iterator it = rowheights.entrySet().iterator(); it.hasNext();) {
599                Map.Entry entry = (Map.Entry) it.next();
600                int cellHeight = ((Integer) entry.getValue()).intValue();
601                maximum_height = Math.max(maximum_height, cellHeight);
602            }
603            return maximum_height;
604        }
605    }
606
607    private class TextAreaEditor extends DefaultCellEditor {
608        private static final long serialVersionUID = 1721646779892184957L;
609
610        public TextAreaEditor(KeyListener keyListener) {
611            super(new JTextField());
612
613            final JTextArea textArea = new JTextArea();
614
615            textArea.addKeyListener(keyListener);
616            textArea.setWrapStyleWord(true);
617            textArea.setLineWrap(true);
618            JScrollPane scrollPane = new JScrollPane(textArea);
619            scrollPane.setBorder(null);
620            editorComponent = scrollPane;
621            delegate = new DefaultCellEditor.EditorDelegate() {
622                private static final long serialVersionUID = 7662356579385373160L;
623
624                @Override
625                public void setValue(Object value) {
626                    textArea.setText((value != null) ? value.toString() : "");
627                }
628
629                @Override
630                public Object getCellEditorValue() {
631                    return textArea.getText();
632                }
633            };
634        }
635    }
636
637    /** RowHeader defines the row header component of the Spreadsheet. */
638    private class RowHeader extends JTable {
639        private static final long serialVersionUID = 2572539746584274419L;
640        private int currentRowIndex = -1;
641        private int lastRowIndex = -1;
642        private JTable parentTable;
643
644        public RowHeader(JTable pTable, Dataset dset) {
645            // Create a JTable with the same number of rows as
646            // the parent table and one column.
647            super(pTable.getRowCount(), 1);
648
649            long[] startArray = dset.getStartDims();
650            long[] strideArray = dset.getStride();
651            int[] selectedIndex = dset.getSelectedIndex();
652            int start = (int) startArray[selectedIndex[0]];
653            int stride = (int) strideArray[selectedIndex[0]];
654
655            // Store the parent table.
656            parentTable = pTable;
657
658            // Set the values of the row headers starting at 0.
659            int n = parentTable.getRowCount();
660            for (int i = 0; i < n; i++) {
661                setValueAt(new Integer(start + indexBase+ i * stride), i, 0);
662            }
663
664            // Get the only table column.
665            TableColumn col = getColumnModel().getColumn(0);
666
667            // Use the cell renderer in the column.
668            col.setCellRenderer(new RowHeaderRenderer());
669        }
670
671        /** Overridden to return false since the headers are not editable. */
672        @Override
673        public boolean isCellEditable(int row, int col) {
674            return false;
675        }
676
677        /** This is called when the selection changes in the row headers. */
678        @Override
679        public void valueChanged(ListSelectionEvent e) {
680            if (parentTable == null) {
681                return;
682            }
683
684            int rows[] = getSelectedRows();
685            if ((rows == null) || (rows.length == 0)) {
686                return;
687            }
688
689            parentTable.clearSelection();
690            parentTable.setRowSelectionInterval(rows[0], rows[rows.length - 1]);
691            parentTable.setColumnSelectionInterval(0, parentTable
692                    .getColumnCount() - 1);
693        }
694
695        @Override
696        protected void processMouseMotionEvent(MouseEvent e) {
697            if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
698                int colEnd = rowAtPoint(e.getPoint());
699
700                if (colEnd < 0) {
701                    colEnd = 0;
702                }
703                if (currentRowIndex < 0) {
704                    currentRowIndex = 0;
705                }
706
707                parentTable.clearSelection();
708
709                if (colEnd > currentRowIndex) {
710                    parentTable
711                            .setRowSelectionInterval(currentRowIndex, colEnd);
712                }
713                else {
714                    parentTable
715                            .setRowSelectionInterval(colEnd, currentRowIndex);
716                }
717
718                parentTable.setColumnSelectionInterval(0, parentTable
719                        .getColumnCount() - 1);
720            }
721        }
722
723        @Override
724        protected void processMouseEvent(MouseEvent e) {
725            int mouseID = e.getID();
726
727            if (mouseID == MouseEvent.MOUSE_CLICKED) {
728                if (currentRowIndex < 0) {
729                    return;
730                }
731
732                if (e.isControlDown()) {
733                    // select discontinguous rows
734                    parentTable.addRowSelectionInterval(currentRowIndex,
735                            currentRowIndex);
736                }
737                else if (e.isShiftDown()) {
738                    // select continguous columns
739                    if (lastRowIndex < 0) {
740                        parentTable.addRowSelectionInterval(0, currentRowIndex);
741                    }
742                    else if (lastRowIndex < currentRowIndex) {
743                        parentTable.addRowSelectionInterval(lastRowIndex,
744                                currentRowIndex);
745                    }
746                    else {
747                        parentTable.addRowSelectionInterval(currentRowIndex,
748                                lastRowIndex);
749                    }
750                }
751                else {
752                    // clear old selection and set new column selection
753                    parentTable.clearSelection();
754                    parentTable.setRowSelectionInterval(currentRowIndex,
755                            currentRowIndex);
756                }
757
758                lastRowIndex = currentRowIndex;
759
760                parentTable.setColumnSelectionInterval(0, parentTable
761                        .getColumnCount() - 1);
762            }
763            else if (mouseID == MouseEvent.MOUSE_PRESSED) {
764                currentRowIndex = rowAtPoint(e.getPoint());
765            }
766        }
767    } // private class RowHeader extends JTable
768
769    /**
770     * RowHeaderRenderer is a custom cell renderer that displays cells as
771     * buttons.
772     */
773    private class RowHeaderRenderer extends JLabel implements TableCellRenderer {
774        private static final long serialVersionUID = 3081275694689434654L;
775
776        public RowHeaderRenderer() {
777            super();
778            setHorizontalAlignment(SwingConstants.CENTER);
779
780            setOpaque(true);
781            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
782            setBackground(Color.lightGray);
783        }
784
785        /** Configures the button for the current cell, and returns it. */
786        public Component getTableCellRendererComponent(JTable table,
787                Object value, boolean isSelected, boolean hasFocus, int row,
788                int column) {
789            setFont(table.getFont());
790
791            if (value != null) {
792                setText(value.toString());
793            }
794
795            return this;
796        }
797    } // private class RowHeaderRenderer extends JLabel implements
798      // TableCellRenderer
799
800}