/*
* Copyright 2008-2014 by Emeric Vernat
*
* This file is part of Java Melody.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bull.javamelody.swing.table;
import java.awt.Component;
import java.awt.Container;
import java.awt.Event;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.text.JTextComponent;
/**
* Composant Table basique servant de base à MListTable.
*
* @author Emeric Vernat
*/
public class MBasicTable extends JTable {
private static final int ADJUST_COLUMN_WIDTHS_MAX_ROWS = 50;
private static final long serialVersionUID = 1L;
// Singleton statique pour renderers par défaut des cellules.
@SuppressWarnings("all")
private static final Map<Class<?>, TableCellRenderer> DEFAULT_RENDERERS = new HashMap<Class<?>, TableCellRenderer>(
25);
@SuppressWarnings("all")
private static final KeyHandler KEY_HANDLER = new KeyHandler();
/**
* KeyAdapter.
*
* @author Emeric Vernat
*/
private static class KeyHandler extends KeyAdapter {
/**
* Constructeur.
*/
KeyHandler() {
super();
}
@Override
public void keyPressed(final KeyEvent event) {
if (event.getSource() instanceof MBasicTable) {
((MBasicTable) event.getSource()).keyPressed(event);
}
}
}
private static class TableHeader extends JTableHeader {
private static final long serialVersionUID = 1L;
TableHeader(TableColumnModel columnModel) {
super(columnModel);
}
@Override
public String getToolTipText(MouseEvent e) {
final Point p = e.getPoint();
final int index = columnModel.getColumnIndexAtX(p.x);
return String.valueOf(columnModel.getColumn(index).getHeaderValue());
}
}
/**
* Constructeur.
*
* @param dataModel
* Modèle pour les données (par exemple, MTableModel)
*/
public MBasicTable(final TableModel dataModel) {
super(dataModel);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// setAutoCreateColumnsFromModel(false);
// par défaut, on laisse AUTO_RESIZE_SUBSEQUENT_COLUMNS
// setAutoResizeMode(AUTO_RESIZE_OFF);
// setDragEnabled(true);
addKeyListener(KEY_HANDLER);
}
/**
* Adapte les largeurs des colonnes selon les données de cette table. <br/>
* Pour chaque colonne la taille préférée est déterminée selon la valeur (et le renderer) du header et selon la valeur (et le renderer) de chaque cellule.
*/
public void adjustColumnWidths() {
if (ADJUST_COLUMN_WIDTHS_MAX_ROWS > 0) {
TableColumn tableColumn;
TableCellRenderer renderer;
Object value;
Component component;
final int columnCount = getColumnCount();
final int rowCount = Math.min(getRowCount(), ADJUST_COLUMN_WIDTHS_MAX_ROWS);
int columnWidth;
final int maxWidth = 250; // taille ajustée maximum (15 minimum par défaut)
// Boucle sur chaque colonne et chaque ligne.
// Trouve le max de la largeur du header et de chaque cellule
// et fixe la largeur de la colonne en fonction.
for (int colNum = 0; colNum < columnCount; colNum++) {
tableColumn = columnModel.getColumn(colNum);
if (tableColumn.getMaxWidth() <= 0) {
continue; // colonne invisible
}
renderer = getTableHeader().getDefaultRenderer();
value = tableColumn.getHeaderValue();
component = renderer.getTableCellRendererComponent(this, value, false, false, -1,
colNum);
columnWidth = component.getPreferredSize().width + 10;
renderer = getCellRenderer(-1, colNum);
for (int rowNum = 0; rowNum < rowCount; rowNum++) {
value = getValueAt(rowNum, colNum);
component = renderer.getTableCellRendererComponent(this, value, false, false,
rowNum, colNum);
columnWidth = Math.max(columnWidth, component.getPreferredSize().width);
}
columnWidth = Math.min(maxWidth, columnWidth);
tableColumn.setPreferredWidth(columnWidth + getIntercellSpacing().width);
}
}
}
@Override
protected void configureEnclosingScrollPane() {
// Si cette table est la viewportView d'un JScrollPane (la situation habituelle),
// configure ce ScrollPane en positionnant la barre verticale à "always"
// (et en installant le tableHeader comme columnHeaderView).
super.configureEnclosingScrollPane();
final Container parent = getParent();
if (parent instanceof JViewport && parent.getParent() instanceof JScrollPane) {
final JScrollPane scrollPane = (JScrollPane) parent.getParent();
if (scrollPane.getVerticalScrollBar() != null) {
scrollPane.getVerticalScrollBar().setFocusable(false);
}
if (scrollPane.getHorizontalScrollBar() != null) {
scrollPane.getHorizontalScrollBar().setFocusable(false);
}
final JViewport viewport = scrollPane.getViewport();
if (viewport == null || viewport.getView() != this) {
return;
}
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
}
}
@SuppressWarnings("unchecked")
@Override
protected void createDefaultRenderers() {
final Map<Class<?>, TableCellRenderer> map = getDefaultTableCellRenderers();
super.defaultRenderersByColumnClass = new Hashtable<Class<?>, TableCellRenderer>(map.size());
super.defaultRenderersByColumnClass.putAll(map);
}
@Override
protected JTableHeader createDefaultTableHeader() {
return new TableHeader(columnModel);
}
/**
* Retourne la map par défaut des renderers de cellules avec pour clé le type des valeurs.
*
* @return Map
*/
private Map<Class<?>, TableCellRenderer> getDefaultTableCellRenderers() {
if (DEFAULT_RENDERERS.isEmpty()) {
DEFAULT_RENDERERS.put(Boolean.class, new MBooleanTableCellRenderer());
DEFAULT_RENDERERS.put(Double.class, new MDoubleTableCellRenderer());
DEFAULT_RENDERERS.put(Float.class, new MDoubleTableCellRenderer());
DEFAULT_RENDERERS.put(java.math.BigDecimal.class, new MDoubleTableCellRenderer());
DEFAULT_RENDERERS.put(Integer.class, new MIntegerTableCellRenderer());
DEFAULT_RENDERERS.put(Long.class, new MIntegerTableCellRenderer());
DEFAULT_RENDERERS.put(java.math.BigInteger.class, new MIntegerTableCellRenderer());
DEFAULT_RENDERERS.put(GregorianCalendar.class, new MDateTableCellRenderer());
DEFAULT_RENDERERS.put(Date.class, new MDateTableCellRenderer());
DEFAULT_RENDERERS.put(java.sql.Date.class, new MDateTableCellRenderer());
DEFAULT_RENDERERS.put(java.sql.Timestamp.class, new MDateTableCellRenderer());
DEFAULT_RENDERERS.put(ArrayList.class, new MListTableCellRenderer()); // NOPMD
DEFAULT_RENDERERS.put(HashMap.class, new MListTableCellRenderer()); // NOPMD
DEFAULT_RENDERERS.put(Hashtable.class, new MListTableCellRenderer()); // NOPMD
DEFAULT_RENDERERS.put(Object.class, new MDefaultTableCellRenderer());
DEFAULT_RENDERERS.put(String.class, new MDefaultTableCellRenderer());
DEFAULT_RENDERERS.put(ImageIcon.class, new MImageIconTableCellRenderer());
DEFAULT_RENDERERS.put(Component.class, new MComponentTableCellRenderer());
}
return DEFAULT_RENDERERS;
}
/** {@inheritDoc} */
@Override
public TableCellRenderer getDefaultRenderer(final Class<?> columnClass) {
// si c'est une instance de Collection on prend le renderer de ArrayList
// (car par ex., le résultat de Collections.unmodifiableList est une interface de List
// sans classe "connue" donc son renderer par défaut n'est pas paramétrable)
if (columnClass != null && Collection.class.isAssignableFrom(columnClass)) {
return super.getDefaultRenderer(ArrayList.class); // NOPMD
}
return super.getDefaultRenderer(columnClass);
}
/** {@inheritDoc} */
@Override
public String getToolTipText(final MouseEvent event) {
final int row = rowAtPoint(event.getPoint());
final int column = columnAtPoint(event.getPoint());
if (row != -1 && column != -1) {
String tip = super.getToolTipText(event);
if (tip == null) {
tip = getTextAt(row, column);
if (tip == null || tip.length() == 0) { // NOPMD
tip = super.getToolTipText();
}
}
return tip;
}
return super.getToolTipText();
}
/**
* Renvoie le texte affiché à la position demandée.
*
* @return String
* @param row
* int
* @param column
* int
*/
public String getTextAt(final int row, final int column) {
final Object value = getValueAt(row, column);
String text = "";
if (value != null) {
final TableCellRenderer renderer = getCellRenderer(row, column);
final Component rendererComponent = renderer.getTableCellRendererComponent(this, value,
false, false, row, column);
if (rendererComponent instanceof JLabel) {
text = ((JLabel) rendererComponent).getText();
text = getToolTipTextIfNoText(text, rendererComponent);
} else if (rendererComponent instanceof JTextComponent) {
text = ((JTextComponent) rendererComponent).getText();
text = getToolTipTextIfNoText(text, rendererComponent);
} else if (rendererComponent instanceof JCheckBox) {
text = String.valueOf(((JCheckBox) rendererComponent).isSelected() ? "vrai"
: "faux");
} else {
text = value.toString();
}
}
return text;
}
private String getToolTipTextIfNoText(final String text, final Component rendererComponent) {
if (text == null || text.length() == 0) {
String toolTipText = ((JComponent) rendererComponent).getToolTipText();
if (toolTipText == null) {
toolTipText = "";
}
return toolTipText;
}
return text;
}
/**
* Gestion des événements clavier sur cette table.
*
* @param event
* KeyEvent
*/
protected void keyPressed(final KeyEvent event) {
final int keyCode = event.getKeyCode();
final int modifiers = event.getModifiers();
if ((modifiers & Event.CTRL_MASK) != 0 && keyCode == KeyEvent.VK_ADD) {
adjustColumnWidths();
}
// else if (modifiers == 0)
// {
// final int selectedColumn = getSelectedColumn() != -1 ? getSelectedColumn() : 0;
// final int selectedRow = getSelectedRow() != -1 ? getSelectedRow() : 0;
// final int rowCount = getRowCount();
// if (isCellEditable(selectedRow, selectedColumn) || rowCount == 0)
// {
// return;
// }
// final String keyChar = String.valueOf(event.getKeyChar());
// String text;
// for (int i = selectedRow + 1; i < rowCount; i++)
// {
// text = getTextAt(i, selectedColumn);
// if (text != null && text.regionMatches(true, 0, keyChar, 0, 1))
// {
// setRowSelectionInterval(i, i);
// setColumnSelectionInterval(selectedColumn, selectedColumn);
// scrollRectToVisible(getCellRect(i, selectedColumn, true));
// return;
// }
// }
// for (int i = 0; i <= selectedRow; i++)
// {
// text = getTextAt(i, selectedColumn);
// if (text != null && text.regionMatches(true, 0, keyChar, 0, 1))
// {
// setRowSelectionInterval(i, i);
// setColumnSelectionInterval(selectedColumn, selectedColumn);
// scrollRectToVisible(getCellRect(i, selectedColumn, true));
// return;
// }
// }
// }
}
}