/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration) * and Cosylab 2002, All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.logging.table; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.swing.DefaultListSelectionModel; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.ProgressMonitor; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.event.RowSorterEvent; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import com.cosylab.logging.LoggingClient; import com.cosylab.logging.client.EntryTypeIcon; import com.cosylab.logging.engine.FiltersVector; import com.cosylab.logging.engine.log.ILogEntry; import com.cosylab.logging.engine.log.LogField; import com.cosylab.logging.engine.log.LogTypeHelper; import com.cosylab.logging.settings.FieldChooserDialog; import com.cosylab.logging.settings.FieldChooserDialog.DialogExitAction; import com.cosylab.logging.viewcoordination.ViewCoordinator; import com.cosylab.logging.viewcoordination.ViewCoordinator.SingleLogSelectionListener; import alma.acs.gui.util.threadsupport.EDTExecutor; import alma.acs.logging.archive.zoom.ZoomManager; import alma.acs.logging.archive.zoom.ZoomProgressListener; import alma.acs.logging.table.renderer.DateRenderer; import alma.acs.logging.table.renderer.EntryTypeRenderer; import alma.acs.logging.table.renderer.InfoRenderer; import alma.acs.util.IsoDateFormat; /** * Subclasses JTable allowing grouping and sorting depending on user's input. * Creation date: (11/11/2001 13:45:22) * @author: */ public class LogEntryTable extends JTable implements ZoomProgressListener { private TableColumn[] columnsList; private boolean[] visibleColumns; private FieldChooserDialog fieldChooser = null; private LoggingClient loggingClient; private DefaultListSelectionModel selectionModel; /** * The object to sort, order and filter the logs shown by the table */ private LogTableRowSorter rowSorter; /** * The index (in the view!!!) of the row selected by the user. * <P> * <code>-1</code> means no row selected. */ private int selecteViewdRow =- 1; /** * The index (in the model!!!) of the row selected by the user. * <P> * <code>-1</code> means no row selected. */ private int selecteModelRow =- 1; /** * The key of the last log selected by the user. * <P> * <code>-1</code> means no log selected. */ private int selecteLogKey =- 1; /** * The renderer to show the date (short or complete format) */ private DateRenderer dateRenderer; /** * The renderer to show the type of log (icon and description or icon only) */ private EntryTypeRenderer logTypeRenderer; /** * The dialog to show (and stop) the progress of the zoom */ private ProgressMonitor zoomProgressMonitor=null; /** * The total number of files to read while zooming */ private int zoomTotFiles; private volatile SingleLogSelectionListener listener; /** * Popup menu used to display the column options for the table. * It is displayed when the user presses the right mouse button over the * header of the table * * Creation date: (1/25/02 11:16:28 AM) * @author: acaproni */ public class ColumnMenu extends JPopupMenu { private String[] menuNames = { "Sort ascending", "Sort descending", "-", "Remove column", "Column chooser", "-", "Group by column", "Ungroup" }; private final int itemCount = menuNames.length; private int columnIndex = 0; private JMenuItem[] items = new JMenuItem[itemCount]; private class MenuClicked implements ActionListener { public void actionPerformed(ActionEvent e) { LogEntryTable let = LogEntryTable.this; ColumnMenu menu = ColumnMenu.this; switch (ColumnMenu.this.findItem(e.getSource())) { case -1 : break; case 0 : let.setOrdering(menu.getColumnIndex(),true); break; case 1 : let.setOrdering(menu.getColumnIndex(),false); break; case 3 : let.hideColumn(menu.getColumnIndex() + 1); break; case 4 : let.showFieldChooser(); break; case 6 : //let.setGroupIndex(menu.getColumnIndex()); break; case 7 : //let.setGroupIndex(-1); } } } /** * Constructs new column popup menu. */ public ColumnMenu() { super(); JMenuItem menuItem = null; for (int i = 0; i < itemCount; i++) { if (menuNames[i].equals("-")) { addSeparator(); } else { menuItem = new JMenuItem(); menuItem.setText(menuNames[i]); menuItem.addActionListener(new MenuClicked()); items[i] = menuItem; add(menuItem); } } } public void setColumnIndex(int index) { columnIndex = index; } public int getColumnIndex() { return columnIndex; } /** * Finds the index of the menu item that was clicked. This value * is used internally to perform appropriate actions. * Creation date: (1/25/02 11:35:04 AM) * @return int * @param source java.lang.Object */ protected int findItem(Object source) { int i = 0; while (i < itemCount) { if (items[i] == source) return i; i++; } return -1; } } /** * Get events from mouse * * @author acaproni * */ private class TableMouseAdapter extends MouseAdapter { private TablePopupMenu popupMenu = new TablePopupMenu(loggingClient,LogEntryTable.this); @Override public void mousePressed(MouseEvent e) { // Get the index of the row and the column below // the mouse pointer int col = getColumnModel().getColumnIndexAtX(e.getX()); int row = LogEntryTable.this.rowAtPoint(e.getPoint()); // Handle the specific event for each mouse button if (e.isPopupTrigger()) { // The mouse button is the pop up trigger so we show the menu // but only if the user selected at least one row if (getSelectedRowCount()>0) { String textUnderMouse = getCellStringContent(row, col); popupMenu.show(LogEntryTable.this,e.getX(),e.getY(),row,textUnderMouse); } } loggingClient.getToolBar().setZoomable(getSelectedRowCount()>1 && loggingClient.getZoomManager().isAvailable()); } } /** * LogEntryTable constructor. * * @param logClient The LoggingClient that owns this table * @param initialDateFormat The format to show the date (true means short) * @param initalLogTypeFormat If <code>true</code> shows the written close * to the icon of the log type */ public LogEntryTable(LoggingClient client,boolean initialDateFormat, boolean initalLogTypeFormat) throws Exception { super(); if (client==null) { throw new IllegalArgumentException("Invalid null LoggingClient!"); } loggingClient=client; // Set the table model LogTableDataModel model= new LogTableDataModel(client); setModel(model); model.start(); // Initialize the sorter (unsorted/unfiltered rowSorter = new LogTableRowSorter(model); EDTExecutor.instance().execute(new Runnable() { @Override public void run() { setRowSorter(rowSorter); } }); // Initially sort by timestamp final List<RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>(); sortKeys.add(new RowSorter.SortKey(LogField.TIMESTAMP.ordinal()+1, SortOrder.DESCENDING)); EDTExecutor.instance().execute(new Runnable() { @Override public void run() { rowSorter.setSortKeys(sortKeys); } }); initialize(initialDateFormat,initalLogTypeFormat); } /** * Utility method to provide mapping of column index to data fields. * Creation date: (1/25/02 11:11:43 AM) * @return int * @param index int */ protected int columnToModel(int index) { return convertColumnIndexToModel(index) - 1; } /** * Insert the method's description here. * Creation date: (2/7/02 3:52:36 PM) * @return java.lang.String */ public org.w3c.dom.Node getExtraInfo() { // String noInfoString = ; // org.w3c.dom.Node nn = new org.w3c.dom.Node.; // DataNode dn = new DataNode(null); // dn.setNodeValue("No additional information"); int index = getSelectedRow(); if (index < 0) return null; ILogEntry additionalInfo = getLCModel().getVisibleLogEntry(index); if (!additionalInfo.hasDatas()) { return null; } return null; //additionalInfo.getDatas().item(0); } /** * Returns the LogTableDataModel. This is a convenience method that returns properly * case data model. * * Creation date: (11/24/2001 18:44:41) * @return com.cosylab.logging.client.LCLogTableDataModel */ public LogTableDataModel getLCModel() { return (LogTableDataModel) getModel(); } /** * Sets the index of the column the table should be sorted by. * If the table is currently unsorted, the result is -1. * * @return int */ public int getSortIndex() { return getLCModel().getFieldSortNumber(); } /** * Hides a table column specified by index. * Creation date: (12/4/2001 22:57:58) * @param columnIndex int */ public void hideColumn(int columnIndex) { if ((columnIndex > 0) && (columnIndex < columnsList.length)) { getColumnModel().removeColumn(columnsList[columnIndex]); visibleColumns[columnIndex] = false; } } /** * Sets a tool tip on all the cells. It pops up when the value is not fully displayed while the mose * scrolls over it. * @see javax.swing.JTable#prepareRenderer(TableCellRenderer, int, int) */ public Component prepareRenderer(TableCellRenderer renderer, int rowIndex, int vColIndex) { String tooltipTxt = getCellStringContent(rowIndex, vColIndex); Component c; try { c = super.prepareRenderer(renderer, rowIndex, vColIndex); } catch (ArrayIndexOutOfBoundsException e) { return null; } LogTooltipHelper.setToolTip((JComponent)c,tooltipTxt,96); return c; } /** * Get a string representing the content of a cell * * @param row The table row of the cell * @param col The table column of the cell * * @return A string representing the content of the cell */ private String getCellStringContent(int row, int col) { Object value = getValueAt(row, col); if (value == null) { return ""; } String tempStr = ""; if (value instanceof Date) { IsoDateFormat sdf = new IsoDateFormat(); tempStr = sdf.format(value); } else if (value instanceof Integer) { if (getColumnName(col).compareTo(LogField.ENTRYTYPE.getName())==0) { tempStr=LogTypeHelper.values()[((Integer)value).intValue()].logEntryType; } else { tempStr = value.toString(); } } else { tempStr = value.toString(); } return tempStr; } /** * Computes the total width of the table taking into consideration the visible columns' width. * @return number of columns */ public int getColumnWidth(int n) { int width = 0; for (int i = 0; i < n; i++) { width = width + getColumnModel().getColumn(i).getWidth(); } return width; } /** * Assigns the additional width that is left to the right to Log Message column. * @param width * @param number of columns * @return TableColumn */ public void setAdditionalWidth(int n, int width) { for (int i = 0; i < n; i++) { TableColumn tc = getColumnModel().getColumn(i); if ((tc.getHeaderValue()).equals("Log Message")) { tc.setPreferredWidth(tc.getWidth() + width); } } } /** * Setup the table * * @param shortDateFormat The format to show the date (if <code>true</code> is short, otherwise complete) * @param logTypeformat The way to show the log type (if <code>true</code> the description is shown) */ private void initialize(boolean shortDateFormat, boolean logTypeformat) { createDefaultColumnsFromModel(); setShowHorizontalLines(false); TableColumnModel tcm = getColumnModel(); // Setup the first col TableColumn tc; // Setup the first col tc = tcm.getColumn(0); tc.setCellRenderer(new InfoRenderer()); tc.setWidth(18); tc.setMaxWidth(18); tc.setResizable(false); tc = tcm.getColumn(LogField.ENTRYTYPE.ordinal() + 1); logTypeRenderer = new EntryTypeRenderer(logTypeformat); tc.setCellRenderer(logTypeRenderer); tc = tcm.getColumn(LogField.TIMESTAMP.ordinal() + 1); dateRenderer = new DateRenderer(shortDateFormat); tc.setCellRenderer(dateRenderer); int n = tcm.getColumnCount(); columnsList = new TableColumn[n]; visibleColumns = new boolean[n]; for (int i = 0; i < n; i++) { columnsList[i] = tcm.getColumn(i); visibleColumns[i] = true; if (i == LogField.LOGMESSAGE.ordinal()+1) { columnsList[i].setPreferredWidth(250); } } setAutoResizeMode(JTable.AUTO_RESIZE_OFF); sizeColumnsToFit(JTable.AUTO_RESIZE_OFF); // Hide some columns (default at startup) hideColumn(LogField.LINE.ordinal()+1); hideColumn(LogField.ROUTINE.ordinal()+1); hideColumn(LogField.HOST.ordinal()+1); hideColumn(LogField.PROCESS.ordinal()+1); hideColumn(LogField.CONTEXT.ordinal()+1); hideColumn(LogField.THREAD.ordinal()+1); hideColumn(LogField.LOGID.ordinal()+1); hideColumn(LogField.PRIORITY.ordinal()+1); hideColumn(LogField.URI.ordinal()+1); hideColumn(LogField.STACKID.ordinal()+1); hideColumn(LogField.FILE.ordinal()+1); hideColumn(LogField.STACKLEVEL.ordinal()+1); hideColumn(LogField.AUDIENCE.ordinal()+1); hideColumn(LogField.ARRAY.ordinal()+1); hideColumn(LogField.ANTENNA.ordinal()+1); // Build and set the selection model selectionModel = new DefaultListSelectionModel(); selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); setSelectionModel(selectionModel); addMouseListener(new TableMouseAdapter()); } /** * Returns true if the elements are sorted in ascending order and false if in descending. * If the table is not sorted, this parameter has no meaning. * Creation date: (1/24/02 10:49:21 AM) * @return boolean */ public boolean isSortAscending() { return getLCModel().sortedAscending(); } /** * Sets the column the elements in this table should be sorted by. * Setting it to -1 means the table is not sorted. * * To change both the index and the order, it is better * to execute setOrdering * * @param index int */ public void setSortIndex(final int index) { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { LogTableDataModel ltdm = getLCModel(); ltdm.setSortComparator(index,ltdm.sortedAscending()); } }); } /** * Changes the order in which the elements are sorted. * Set true for ascending and false for descending. * * To change both the index and the order, it is better * to execute setOrdering * * @param ascending The order for the table */ public void setSortOrder(final boolean ascending) { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { LogTableDataModel ltdm = getLCModel(); ltdm.setSortComparator(ltdm.getFieldSortNumber(),ascending); } }); } /** * Set the order and the field for ordering at once. * Calling this method is faster the calling the setSortIndex * and setSortOrder * * @param field The field of the logs for ordering * -1 disable the ordering * @param ascending The order ascending(true)/descending (false) * */ public void setOrdering(final int field, final boolean ascending) { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { getLCModel().setSortComparator(field,ascending); } }); } /** * Displays the column specified by index. * Creation date: (12/4/2001 22:56:11) * @param columnIndex int */ public void showColumn(int columnIndex) { if ((columnIndex > 0) && (columnIndex < columnsList.length)) { TableColumnModel tcm = getColumnModel(); if (!visibleColumns[columnIndex]) { tcm.addColumn(columnsList[columnIndex]); visibleColumns[columnIndex] = true; } int w = columnsList[0].getWidth(); (columnsList[0]).setWidth(w + 1); (columnsList[0]).setWidth(w); } } /** * Displays the field chooser dialog. * <p> * Creation date: (1/2/2002 23:20:27) */ public void showFieldChooser() { String[] fieldNames = new String[LogField.values().length]; int t=0; for (LogField f: LogField.values()) { fieldNames[t++]=f.getName(); } boolean[] fieldVisible = getVisibleColumns(true); if (fieldChooser==null) { fieldChooser = new FieldChooserDialog(loggingClient); } fieldChooser.setupFields(fieldNames, fieldVisible); fieldChooser.setVisible(true); if (fieldChooser.getModalResult()==DialogExitAction.CANCEL) { return; } boolean[] newFields = fieldChooser.getFields(); for (int i = 0; i < LogField.values().length; i++) { if (newFields[i] != fieldVisible[i]) { if (newFields[i]) showColumn(i + 1); else hideColumn(i + 1); } } } /** * Returns an array of boolean with true for each visible column * The visibleComun array of this class starts counting the column * from 1 instead of 0 (i.e. 0 is not used) * * @param zeroBased If true the number 0 correspond to the first column * (the array of visible columns uses 1 for the first column) * @return An array of boolean describing which columns are displayed */ public boolean[] getVisibleColumns(boolean zeroBased) { int nFields = LogField.values().length; // The array of visible columns to return boolean[] visibleCols = new boolean[nFields]; int pad =(!zeroBased)?0:1; for (int i = 0; i < nFields; i++) { visibleCols[i] = visibleColumns[i + pad]; } return visibleCols; } /** * Close the filterChooser dialog releasing all the resources. * This is intended to be the last operatione when the application * is closing * */ public void close() { if (fieldChooser!=null) { fieldChooser.setVisible(false); fieldChooser.dispose(); fieldChooser=null; } } /** * Set the format used to show the timestamp in the date column * * @param shortFormat The format of the date (true means short, false means complete) */ public void setShortDateFormat(boolean shortFormat) { dateRenderer.setShortDateFormat(shortFormat); this.repaint(); } /** * Show/Hide the description of the log type at the right side of the icon. * <P> * When the description is disabled, the icon appear without the written. * The column is also resized to use less space as possible. * * @param showDescription If <code>true</code> a string with the description * of the log appear at the right side of the icon (default) */ public void setLogTypeDescriptionView(boolean showDescription) { logTypeRenderer.viewDescription(showDescription); TableColumn logTypeCol = columnsList[LogField.ENTRYTYPE.ordinal()+1]; if (!showDescription) { logTypeCol.setPreferredWidth(EntryTypeIcon.INFO_ICON.icon.getIconWidth()); } else { logTypeCol.setPreferredWidth(4*EntryTypeIcon.INFO_ICON.icon.getIconWidth()); } this.repaint(); } /** * Override the method in <code>JTable</code> to catch the change of selection operated * by the user and update the detailed log info accordingly. * * @see <code>JTable.changeSelection(int rowIndex, int columnIndex, boolean toggle,boolean extend)</code> */ @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { super.changeSelection(rowIndex, columnIndex, toggle, extend); SingleLogSelectionListener listenerCopy = listener; // to avoid concurrency issues if (rowIndex!=-1 && !toggle && !extend) { LogTableDataModel model =(LogTableDataModel)getModel(); ILogEntry log = model.getVisibleLogEntry(convertRowIndexToModel(rowIndex)); loggingClient.setLogDetailContent(log); selecteViewdRow=rowIndex; selecteModelRow=convertRowIndexToModel(rowIndex); selecteLogKey=((LogTableDataModel)getModel()).getLogKey(selecteModelRow); if (listenerCopy != null && log != null) { listenerCopy.notifyLogSelected(log); } } else { // Multiple line selection etc all mean 'deselection' for our SingleLogSelectionListener if (listenerCopy != null) { listenerCopy.notifyLogSelected(null); } } } /** * This is used only by {@link ViewCoordinator}. */ public void setLogSelectionListener(SingleLogSelectionListener listener) { this.listener = listener; } /** * Scroll the table to the next selected row and select it. * <P> * If there is no row selected or the selected line is the last row of the table, * the method return without scrolling the table. */ public void scrollToNextSelectedRow() { if (selecteViewdRow==-1 || selecteViewdRow==getRowCount()-1) { return; } changeSelection(selecteViewdRow+1,1,false,false); } /** * Scroll the table to the next selected row and select it. * <P> * If there is no row selected or the selected row is the first row of the table, * the method return without scrolling the table */ public void scrollToPrevSelectedRow() { if (selecteViewdRow<=0) { return; } changeSelection(selecteViewdRow-1,1,false,false); } /** * Scroll the table to the first row */ public void scrollToFirstRow() { changeSelection(0,1,false,false); } /** * Scroll the table to the last row */ public void scrollToLastRow() { changeSelection(getRowCount()-1,1,false,false); } /** * Scroll the table to the selected row * <P> * If there is no selected row, the method return without scrolling. * <P> * The selected row could not be anymore present in the table for example because it has * been deleted by the <code>LogDeleter</code> or the table has been cleared by the user. */ public void scrollToSelectedRow() { if (selecteViewdRow==-1) { return; } EDTExecutor.instance().execute(new Runnable() { @Override public void run() { int modelRow=((LogTableDataModel)getModel()).findKeyPos(selecteLogKey); if (modelRow!=-1) { changeSelection(convertRowIndexToView(modelRow),1,false,false); } } }); } /** * Return the filters defined by the user. * * @return The user defined filters */ public FiltersVector getFilters() { return rowSorter.getFilters(); } /** * * @param newFilters * @param append */ public void setFilters(final FiltersVector newFilters, final boolean append) { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { rowSorter.setFilters(newFilters, append); } }); } /** * * @return A description of the active filters * @see FiltersVector.getFilterString() */ public String getFiltersString() { return rowSorter.getFiltersString(); } /** * Set the new log level i.e. the level of the logs shown in the table. * * @param newLevel */ public void setLogLevel(final LogTypeHelper newLevel) { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { rowSorter.setLogLevel(newLevel); } }); rowSorter.setLogLevel(newLevel); } /** * Shows the number of rows shown by the table. * * @return the number of rows shown by the table. */ public int getViewRowCount() { return rowSorter.getViewRowCount(); } /* (non-Javadoc) * @see javax.swing.JTable#sorterChanged(javax.swing.event.RowSorterEvent) */ @Override public void sorterChanged(RowSorterEvent e) { if (e.getType()==RowSorterEvent.Type.SORT_ORDER_CHANGED) { setVisible(false); loggingClient.setEnabled(false); super.sorterChanged(e); loggingClient.animateProgressBar("Sorting"); } else { if (e.getType()==RowSorterEvent.Type.SORTED) { loggingClient.freezeProgressBar(); super.sorterChanged(e); loggingClient.setEnabled(true); setVisible(true); } } } /** * Zoom over the selected logs. * <P> * The zoom consists of loading all the logs in the time interval * defined by the selected logs. * The zoom is delegated to the <code>ZoomManager</code>. * * @see {@link ZoomManager} */ public void zoom() { if (getSelectedRowCount()<=1) { return; } int[] indexes = getSelectedRows(); long startDate=Long.MAX_VALUE; long endDate=0; zoomTotFiles=0; System.out.println("Keys "+indexes.length); for (int i: indexes) { ILogEntry log= getLCModel().getVisibleLogEntry(convertRowIndexToModel(i)); long time =((Long)log.getField(LogField.TIMESTAMP)).longValue(); if (time<startDate) { startDate=time; } if (time>endDate) { endDate=time; } } String startDateStr=IsoDateFormat.formatDate(new Date(startDate)); String endDateStr=IsoDateFormat.formatDate(new Date(endDate)); System.out.println("Zooming from "+startDateStr+" to "+endDateStr); try { loggingClient.getZoomManager().zoom(startDateStr, endDateStr, loggingClient, this, loggingClient); } catch (Throwable t) { JOptionPane.showMessageDialog( loggingClient, "Error while loading logs.\n"+t.getMessage(), "Zoom error", JOptionPane.ERROR_MESSAGE); } if (zoomProgressMonitor!=null) { zoomProgressMonitor.close(); zoomProgressMonitor=null; } } /* (non-Javadoc) * @see alma.acs.logging.archive.zoom.ZoomProgressListener#zoomReadingFile(int) */ @Override public void zoomReadingFile(int num) { if (zoomProgressMonitor!=null) { zoomProgressMonitor.setProgress(num); zoomProgressMonitor.setNote("Reading "+num+"/"+zoomTotFiles); if (zoomProgressMonitor.isCanceled()) { loggingClient.getZoomManager().stopZoom(); } } } /* (non-Javadoc) * @see alma.acs.logging.archive.zoom.ZoomProgressListener#zoomTotalFileToRead(int) */ @Override public void zoomTotalFileToRead(int num) { zoomTotFiles=num; zoomProgressMonitor= new ProgressMonitor(this,"Zoom",null,0,num); System.out.println(""+num+" files to read while zoom"); } /** * Align the text of the table to the top. * <P> * By default the vertical alignment of text in the {@link JLabel} used to show text * in table cells is centered.. This cause a problem when the text is multiline so we have * to force a TOP alignment of the text. * * @see javax.swing.JTable#getCellRenderer(int, int) */ @Override public TableCellRenderer getCellRenderer(int row, int column) { TableCellRenderer temp=super.getCellRenderer(row, column); if (temp instanceof JLabel) { ((JLabel)temp).setVerticalAlignment(JLabel.TOP); } return temp; } }