package org.limewire.ui.swing.advanced.connection;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JToolTip;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.table.TableColumnExt;
import org.limewire.core.api.connection.ConnectionItem;
import org.limewire.ui.swing.advanced.connection.ConnectionTableFormat.ConnectionColumn;
import org.limewire.ui.swing.table.MouseableTable;
import org.limewire.ui.swing.util.I18n;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.swing.DefaultEventTableModel;
/**
* The table that displays the connections details. ConnectionTable installs
* a header popup menu to allow the user to configure the visible columns.
*/
public class ConnectionTable extends MouseableTable {
/** List of connections. */
private TransformedList<ConnectionItem, ConnectionItem> connectionList;
/** Table format for connections details. */
private ConnectionTableFormat tableFormat;
/** Disabled menu item. */
private JMenuItem disabledMenuItem;
/** Context menu for table header. */
private JPopupMenu headerPopup;
/** Text array for connection tooltip. */
private String[] tipArray;
private Action defaultConfigAction = new DefaultConfigAction(I18n.tr("Revert To Default"));
private Action autosortAction = new AutosortAction(I18n.tr("Sort Automatically"));
private Action tooltipsAction = new TooltipsAction(I18n.tr("Extended Tooltips"));
private Action toggleColumnAction = new ToggleColumnAction();
/**
* Constructs the connections detail table.
*/
public ConnectionTable() {
// Set attributes.
setIntercellSpacing(new Dimension(1, 0));
setColumnSelectionAllowed(false);
setRowSelectionAllowed(true);
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setShowHorizontalLines(false);
// Set up table header to display context menu to configure visible
// columns. As recommended in the API Javadoc for isPopupTrigger(),
// we check both the mousePressed and mouseReleased events.
getTableHeader().setToolTipText(I18n.tr("Right-click to select columns to display"));
getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
showHeaderPopup(e);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
showHeaderPopup(e);
}
}
});
}
@Override
protected void setTableHeaderRenderer() {
//use default table headers
}
/**
* Creates the default cell renderers.
*/
@Override
protected void createDefaultRenderers() {
super.createDefaultRenderers();
// Install custom renderer for Object values.
setDefaultRenderer(Object.class, new ObjectCellRenderer());
}
/**
* Creates the tooltip component. This method is called by the tooltip
* manager whenever a new tooltip is needed.
*/
@Override
public JToolTip createToolTip() {
// Create tooltip component.
MultiLineToolTip toolTip = new MultiLineToolTip();
toolTip.setComponent(this);
// Set text array.
toolTip.setToolTipArray(tipArray);
return toolTip;
}
/**
* Returns the tooltip text at the location of the specified mouse event.
*/
@Override
public String getToolTipText(MouseEvent e) {
Point p = e.getPoint();
int row = rowAtPoint(p);
int col = columnAtPoint(p);
// Get tooltip text if action is enabled.
if (Boolean.TRUE.equals(tooltipsAction.getValue(Action.SELECTED_KEY))
&& (row >= 0)) {
// Get connection item and tooltip text array.
ConnectionItem item = connectionList.get(convertRowIndexToModel(row));
tipArray = tableFormat.getToolTipArray(item);
// Return row/col text so tooltip manager will create new tooltip
// for each table cell.
return (row + "," + col);
}
// Reset tooltip array and return null.
tipArray = new String[0];
return null;
}
/**
* Returns true if the table should be sorted in response to the specified
* table model event.
*/
@Override
protected boolean shouldSortOnChange(TableModelEvent e) {
if ((autosortAction == null) ||
(Boolean.TRUE.equals(autosortAction.getValue(Action.SELECTED_KEY)))) {
return super.shouldSortOnChange(e);
} else {
return false;
}
}
/**
* Sets the visibility of the column with the specified name.
*/
public void setColumnVisible(String name, boolean visible) {
// Get column and set visibility.
TableColumnExt column = getColumnExt(name);
column.setVisible(visible);
// Get column title.
String columnTitle = column.getTitle();
// Find checkbox menu item with matching title.
JCheckBoxMenuItem matchingItem = null;
int itemCount = headerPopup.getComponentCount();
for (int i = 0; i < itemCount; i++) {
Component menuComponent = headerPopup.getComponent(i);
if (menuComponent instanceof JCheckBoxMenuItem) {
JCheckBoxMenuItem item = (JCheckBoxMenuItem) menuComponent;
String itemTitle = item.getText();
if (itemTitle.equals(columnTitle)) {
matchingItem = item;
break;
}
}
}
// Select matching menu item.
if (matchingItem != null) {
matchingItem.setSelected(visible);
}
}
/**
* Sets the width of the column with the specified name.
*/
public void setColumnWidth(String name, int width) {
getColumnExt(name).setPreferredWidth(width);
}
/**
* Initializes the data model using the specified connection list and table
* format.
*/
public void setEventList(
TransformedList<ConnectionItem, ConnectionItem> connectionList,
ConnectionTableFormat tableFormat) {
if (tableFormat == null) {
throw new IllegalArgumentException("tableFormat cannot be null");
}
this.connectionList = connectionList;
this.tableFormat = tableFormat;
// Set table model using event list and table format.
setModel(new DefaultEventTableModel<ConnectionItem>(connectionList, tableFormat));
// Create header popup menu.
headerPopup = createHeaderPopup();
// Initialize column visibility.
resetTableColumns();
}
/**
* Clears the data model and releases resources used by the event list.
*/
public void clearEventList() {
// Get table model and dispose resources.
TableModel tableModel = getModel();
if (tableModel instanceof DefaultEventTableModel) {
((DefaultEventTableModel) tableModel).dispose();
}
// Set default model to remove old reference.
setModel(new DefaultTableModel());
// Dispose connection list.
connectionList.dispose();
connectionList = null;
}
/**
* Returns an array of the selected connections.
*/
public ConnectionItem[] getSelectedConnections() {
// Get selected rows.
int[] rows = getSelectedRows();
// Create result array.
ConnectionItem[] items = new ConnectionItem[rows.length];
for (int i = 0; i < rows.length; i++) {
items[i] = connectionList.get(convertRowIndexToModel(rows[i]));
}
// Return result array.
return items;
}
/**
* Updates the table by firing a table model event.
*/
public void refresh() {
TableModel model = getModel();
if (model instanceof AbstractTableModel) {
((AbstractTableModel) model).fireTableRowsUpdated(0, getRowCount() - 1);
}
}
/**
* Resets the table columns and attributes to their default configuration.
*/
public void resetTableColumns() {
// Reset column widths and visibility.
for (int col = 0; col < tableFormat.getColumnCount(); col++) {
ConnectionColumn column = tableFormat.getColumn(col);
setColumnWidth(column.getName(), column.getWidth());
setColumnVisible(column.getName(), column.isVisible());
}
// Reset attributes.
autosortAction.putValue(Action.SELECTED_KEY, Boolean.TRUE);
tooltipsAction.putValue(Action.SELECTED_KEY, Boolean.FALSE);
}
/**
* Creates a custom popup menu for the table header.
*/
private JPopupMenu createHeaderPopup() {
// Create popup menu.
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem defaultItem = new JMenuItem();
defaultItem.setAction(defaultConfigAction);
popupMenu.add(defaultItem);
JMenu optionsMenu = new JMenu(I18n.tr("More Options"));
popupMenu.add(optionsMenu);
popupMenu.addSeparator();
JMenuItem autosortItem = new JCheckBoxMenuItem();
autosortItem.setAction(autosortAction);
optionsMenu.add(autosortItem);
JMenuItem tooltipsItem = new JCheckBoxMenuItem();
tooltipsItem.setAction(tooltipsAction);
optionsMenu.add(tooltipsItem);
// Get column headings in default order.
int columnCount = tableFormat.getColumnCount();
for (int i = 0; i < columnCount; i++) {
// Create checkbox menu item for each column heading.
String headerName = tableFormat.getColumnName(i);
JMenuItem item = new JCheckBoxMenuItem(headerName, true);
item.addActionListener(toggleColumnAction);
popupMenu.add(item);
}
return popupMenu;
}
/**
* Displays the header popup menu at the location of the specified mouse
* event.
*/
private void showHeaderPopup(MouseEvent e) {
if (headerPopup != null) {
headerPopup.show((Component) e.getSource(), e.getX(), e.getY());
}
}
/**
* Updates the header popup menu by enabling/disabling menu items based on
* the number of visible columns.
*/
private void updateHeaderPopup() {
// Get number of visible columns.
int visibleColumnCount = getColumnCount(false);
// If only one visible column, then disable its menu item.
if (visibleColumnCount == 1) {
// Find the only visible column.
int allColumnCount = getColumnCount(true);
TableColumnExt column = null;
for (int i = 0; i < allColumnCount; i++) {
column = getColumnExt(i);
if (column.isVisible()) {
break;
}
}
// Get menu item name from column title.
assert column != null;
String headerName = column.getTitle();
// Find matching menu item, and disable so user cannot hide column.
Component[] components = headerPopup.getComponents();
for (Component component : components) {
if (component instanceof JMenuItem) {
JMenuItem item = (JMenuItem) component;
// Disable matching menu item.
if (item.getText().equals(headerName)) {
item.setEnabled(false);
disabledMenuItem = item;
break;
}
}
}
} else if (disabledMenuItem != null) {
// Re-enable previously disabled menu item because more than one
// column is visible.
disabledMenuItem.setEnabled(true);
}
}
/**
* Action to reset table to default configuration.
*/
private class DefaultConfigAction extends AbstractAction {
public DefaultConfigAction(String name) {
super(name);
}
@Override
public void actionPerformed(ActionEvent e) {
resetTableColumns();
}
}
/**
* Action to toggle option to automatically sort by column.
*/
private static class AutosortAction extends AbstractAction {
public AutosortAction(String name) {
super(name);
putValue(SELECTED_KEY, Boolean.TRUE);
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
/**
* Action to toggle option to display tooltips.
*/
private static class TooltipsAction extends AbstractAction {
public TooltipsAction(String name) {
super(name);
putValue(SELECTED_KEY, Boolean.FALSE);
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
/**
* Action that toggles the visibility of the selected table column.
*/
private class ToggleColumnAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
// Get table column.
String name = e.getActionCommand();
TableColumnExt selectedColumn = getColumnExt(name);
// Toggle column visibility.
boolean visible = selectedColumn.isVisible();
selectedColumn.setVisible(!visible);
// Update popup menu.
updateHeaderPopup();
// Resize table columns to fit.
packAll();
}
}
/**
* Cell renderer for Object values.
*/
private static class ObjectCellRenderer extends DefaultTableRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
// Retrieve renderer component.
Component renderer = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
// Set renderer attributes. The border is set so that the focused
// cell border is never seen.
if (renderer instanceof JLabel) {
((JLabel) renderer).setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
}
return renderer;
}
}
/**
* Tooltip component that displays multiple lines using a text array.
*/
private static class MultiLineToolTip extends JToolTip {
private static final int LINE_LEN = 72;
private String[] tipArray;
/**
* Returns the tooltip text generated using the text array. The
* result is an HTML string. This overrides the superclass method to
* ignore the tip text set by the tooltip manager.
*
* <p>Note that when the mouse moves to a new table cell,
* <code>getToolTipText(MouseEvent)</code> returns a different value,
* causing the tooltip manager to create a new tooltip and set its
* tip text. This method allows the table to control the tooltip,
* which may not change when the mouse moves between cells in the same
* row.</p>
*/
@Override
public String getTipText() {
if ((tipArray != null) && (tipArray.length > 0)) {
// Create buffer to build string.
StringBuilder buf = new StringBuilder("<html>");
boolean firstLine = true;
// Add each text line with breaks between lines. Long lines
// are wrapped onto multiple lines.
for (String text : tipArray) {
while (text.length() > LINE_LEN) {
buf.append(firstLine ? "" : "<br/>");
buf.append(text.substring(0, LINE_LEN));
firstLine = false;
text = text.substring(LINE_LEN);
}
buf.append(firstLine ? "" : "<br/>");
buf.append(text);
firstLine = false;
}
// Return result string.
buf.append("</html>");
return buf.toString();
} else {
return null;
}
}
/**
* Returns the array of text lines in the tooltip.
*/
public String[] getToolTipArray(){
return tipArray;
}
/**
* Sets the array of text lines in the tooltips.
*/
public void setToolTipArray(String[] tipArray){
this.tipArray = tipArray;
}
}
}