/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * SortableAndSearchableTable.java * Copyright (C) 2010-2014 University of Waikato, Hamilton, New Zealand */ package meka.gui.core; import javax.swing.*; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import java.util.Hashtable; import java.util.Vector; /** * A specialized JTable that allows double-clicking on header for resizing to * optimal width, as well as being searchable and sortable. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8807 $ */ public class SortableAndSearchableTable extends MekaTable implements SortableTable, SearchableTable { /** for serialization. */ private static final long serialVersionUID = -3176811618121454828L; /** the key for the sort column setting. */ public static final String KEY_SORTCOL = "sort col"; /** the key for the sort oder. */ public static final String KEY_SORTORDER = "sort order"; /** the key for the search string. */ public static final String KEY_SEARCHSTRING = "search string"; /** the key for the regular expression search flag. */ public static final String KEY_SEARCHREGEXP = "search reg exp"; /** the sortable/searchable model. */ protected SortableAndSearchableWrapperTableModel m_Model; /** whether to automatically set optimal column widths. */ protected boolean m_UseOptimalColumnWidths; /** whether to automatically sort table models that get set via setModel. */ protected boolean m_SortNewTableModel; /** * Constructs a default <code>SortedBaseTable</code> that is initialized with a default * data model, a default column model, and a default selection * model. */ public SortableAndSearchableTable() { super(); } /** * Constructs a <code>SortedBaseTable</code> with <code>numRows</code> * and <code>numColumns</code> of empty cells using * <code>DefaultTableModel</code>. The columns will have * names of the form "A", "B", "C", etc. * * @param numRows the number of rows the table holds * @param numColumns the number of columns the table holds */ public SortableAndSearchableTable(int numRows, int numColumns) { super(numRows, numColumns); } /** * Constructs a <code>SortedBaseTable</code> to display the values in the two dimensional array, * <code>rowData</code>, with column names, <code>columnNames</code>. * <code>rowData</code> is an array of rows, so the value of the cell at row 1, * column 5 can be obtained with the following code: * <p> * <pre> rowData[1][5]; </pre> * <p> * All rows must be of the same length as <code>columnNames</code>. * <p> * @param rowData the data for the new table * @param columnNames names of each column */ public SortableAndSearchableTable(final Object[][] rowData, final Object[] columnNames) { super(rowData, columnNames); } /** * Constructs a <code>SortedBaseTable</code> to display the values in the * <code>Vector</code> of <code>Vectors</code>, <code>rowData</code>, * with column names, <code>columnNames</code>. The * <code>Vectors</code> contained in <code>rowData</code> * should contain the values for that row. In other words, * the value of the cell at row 1, column 5 can be obtained * with the following code: * <p> * <pre>((Vector)rowData.elementAt(1)).elementAt(5);</pre> * <p> * @param rowData the data for the new table * @param columnNames names of each column */ public SortableAndSearchableTable(Vector rowData, Vector columnNames) { super(rowData, columnNames); } /** * Constructs a <code>SortedBaseTable</code> that is initialized with * <code>dm</code> as the data model, a default column model, * and a default selection model. * * @param dm the data model for the table */ public SortableAndSearchableTable(TableModel dm) { super(dm); } /** * Constructs a <code>SortedBaseTable</code> that is initialized with * <code>dm</code> as the data model, <code>cm</code> * as the column model, and a default selection model. * * @param dm the data model for the table * @param cm the column model for the table */ public SortableAndSearchableTable(TableModel dm, TableColumnModel cm) { super(dm, cm); } /** * Constructs a <code>SortedBaseTable</code> that is initialized with * <code>dm</code> as the data model, <code>cm</code> as the * column model, and <code>sm</code> as the selection model. * If any of the parameters are <code>null</code> this method * will initialize the table with the corresponding default model. * The <code>autoCreateColumnsFromModel</code> flag is set to false * if <code>cm</code> is non-null, otherwise it is set to true * and the column model is populated with suitable * <code>TableColumns</code> for the columns in <code>dm</code>. * * @param dm the data model for the table * @param cm the column model for the table * @param sm the row selection model for the table */ public SortableAndSearchableTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { super(dm, cm, sm); } /** * Returns the initial setting of whether to set optimal column widths. * Default implementation returns "false", since large tables might take too * long to be displayed otherwise. * * @return true if optimal column widths are used by default */ protected boolean initialUseOptimalColumnWidths() { return false; } /** * Sets whether to automatically set optimal column widths. * * @param value if true then optimal column widths are used */ public void setUseOptimalColumnWidhts(boolean value) { m_UseOptimalColumnWidths = value; if (m_UseOptimalColumnWidths) { setAutoResizeMode(AUTO_RESIZE_OFF); setOptimalColumnWidth(); } } /** * Returns whether to automatically set optimal column widths. * Default implementation is initialized with "false". * * @return true if optimal column widths are to be used */ public boolean getUseOptimalColumnWidths() { return m_UseOptimalColumnWidths; } /** * Returns the initial setting of whether to sort new models. * Default implementation returns "false". * * @return true if new models need to be sorted */ protected boolean initialSortNewTableModel() { return false; } /** * Sets whether to sort new models. * * @param value if true then new models get sorted */ public void setSortNewTableModel(boolean value) { m_SortNewTableModel = value; if (m_SortNewTableModel) sort(0); } /** * Returns whether to sort new models. * Default implementation is initialized with "false". * * @return true if new models get sorted */ public boolean getSortNewTableModel() { return m_SortNewTableModel; } /** * Initializes some GUI-related things. */ @Override protected void initGUI() { super.initGUI(); m_SortNewTableModel = initialSortNewTableModel(); m_Model.addMouseListenerToHeader(this); if (getSortNewTableModel()) sort(0); m_UseOptimalColumnWidths = initialUseOptimalColumnWidths(); if (getUseOptimalColumnWidths()) { setAutoResizeMode(AUTO_RESIZE_OFF); setOptimalColumnWidth(); } } /** * Returns the class of the table model that the models need to be derived * from. The default implementation just returns TableModel.class * * @return the class the models must be derived from */ protected Class getTableModelClass() { return TableModel.class; } /** * Backs up the settings from the old model. * * @param model the old model (the model stored within the SortedModel) * @return the backed up settings */ protected Hashtable<String,Object> backupModelSettings(TableModel model) { Hashtable<String,Object> result; result = new Hashtable<String,Object>(); result.put(KEY_SORTCOL, m_Model.getSortColumn()); result.put(KEY_SORTORDER, m_Model.isAscending()); if (model instanceof SearchableTableModel) { if (((SearchableTableModel) model).getSeachString() != null) result.put(KEY_SEARCHSTRING, ((SearchableTableModel) model).getSeachString()); result.put(KEY_SEARCHREGEXP, ((SearchableTableModel) model).isRegExpSearch()); } return result; } /** * Restores the settings previously backed up. * * @param model the new model (the model stored within the SortedModel) * @param settings the old settings, null if no settings were available */ protected void restoreModelSettings(TableModel model, Hashtable<String,Object> settings) { int sortCol; boolean asc; String search; boolean regexp; // default values sortCol = 0; asc = true; search = null; regexp = false; // get stored values if (settings != null) { sortCol = (Integer) settings.get(KEY_SORTCOL); asc = (Boolean) settings.get(KEY_SORTORDER); if (model instanceof SearchableTableModel) { search = (String) settings.get(KEY_SEARCHSTRING); regexp = (Boolean) settings.get(KEY_SEARCHREGEXP); } } // restore sorting if (getSortNewTableModel()) m_Model.sort(sortCol, asc); // restore search if (model instanceof SearchableTableModel) ((SearchableTableModel) model).search(search, regexp); // set optimal column widths if (getUseOptimalColumnWidths()) setOptimalColumnWidth(); } /** * Sets the model to display - only {@link #getTableModelClass()}. * * @param model the model to display */ @Override public synchronized void setModel(TableModel model) { Hashtable<String,Object> settings; if (!(getTableModelClass().isInstance(model))) model = createDefaultDataModel(); // backup current setup if (m_Model != null) { settings = backupModelSettings(m_Model); getTableHeader().removeMouseListener(m_Model.getHeaderMouseListener()); } else { settings = null; } m_Model = new SortableAndSearchableWrapperTableModel(model); super.setModel(m_Model); m_Model.addMouseListenerToHeader(this); // restore setup restoreModelSettings(m_Model, settings); } /** * Sets the base model to use. Discards any sorting. * * @param value the base model */ public synchronized void setUnsortedModel(TableModel value) { m_Model.setUnsortedModel(value); } /** * Sets the base model to use. * * @param value the base model * @param restoreSorting whether to restore the sorting */ public synchronized void setUnsortedModel(TableModel value, boolean restoreSorting) { m_Model.setUnsortedModel(value, restoreSorting); } /** * returns the underlying model, can be null. * * @return the current model */ public synchronized TableModel getUnsortedModel() { if (m_Model != null) return m_Model.getUnsortedModel(); else return null; } /** * Returns the actual underlying row the given visible one represents. Useful * for retrieving "non-visual" data that is also stored in a TableModel. * * @param visibleRow the displayed row to retrieve the original row for * @return the original row */ public synchronized int getActualRow(int visibleRow) { return m_Model.getActualRow(visibleRow); } /** * Returns the "visible" row derived from row in the actual table model. * * @param internalRow the row in the actual model * @return the row in the sorted model, -1 in case of an error */ public synchronized int getDisplayRow(int internalRow) { return m_Model.getDisplayRow(internalRow); } /** * returns whether the table was sorted. * * @return true if the table was sorted */ public synchronized boolean isSorted() { return m_Model.isSorted(); } /** * Returns the sort column. * * @return the sort column */ public synchronized int getSortColumn() { return m_Model.getSortColumn(); } /** * Returns whether sorting is ascending or not. * * @return true if ascending * @see #isSorted() * @see #getSortColumn() */ public synchronized boolean isAscending() { return m_Model.isAscending(); } /** * sorts the table over the given column (ascending). * * @param columnIndex the column to sort over */ public synchronized void sort(int columnIndex) { if (m_Model != null) m_Model.sort(columnIndex); } /** * sorts the table over the given column, either ascending or descending. * * @param columnIndex the column to sort over * @param ascending ascending if true, otherwise descending */ public synchronized void sort(int columnIndex, boolean ascending) { if (m_Model != null) m_Model.sort(columnIndex, ascending); } /** * Returns the actual row count in the model. * * @return the row count in the underlying data */ public synchronized int getActualRowCount() { return m_Model.getActualRowCount(); } /** * Performs a search for the given string. Limits the display of rows to * ones containing the search string. * * @param searchString the string to search for * @param regexp whether to perform regular expression matching * or just plain string comparison */ public synchronized void search(String searchString, boolean regexp) { int[] selected; int i; int index; // determine actual selected rows selected = getSelectedRows(); for (i = 0; i < selected.length; i++) selected[i] = getActualRow(selected[i]); m_Model.search(searchString, regexp); // re-select rows that are still in current search clearSelection(); for (i = 0; i < selected.length; i++) { index = getDisplayRow(selected[i]); if (index != -1) getSelectionModel().addSelectionInterval(index, index); } } /** * Returns the current search string. * * @return the search string, null if not filtered */ public synchronized String getSeachString() { return m_Model.getSeachString(); } /** * Returns whether the last search was a regular expression based one. * * @return true if last search was a reg exp one */ public synchronized boolean isRegExpSearch() { return m_Model.isRegExpSearch(); } }