package com.jbidwatcher.ui.table;
import com.jbidwatcher.auction.AuctionEntry;
import com.jbidwatcher.util.Comparison;
import com.jbidwatcher.util.config.JConfig;
import com.jbidwatcher.util.Constants;
import com.jbidwatcher.util.Pair;
import com.jbidwatcher.util.queue.MQFactory;
import com.jbidwatcher.util.queue.MessageQueue;
import com.jbidwatcher.util.queue.SuperQueue;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableCellEditor;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.Point;
import java.awt.Dimension;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: Morgan
* Date: Jun 20, 2008
* Time: 12:06:56 PM
* Handle tooltips, at least. A very cool feature.
*/
public class AuctionTable extends JTable implements MessageQueue.Listener {
private static TableCellEditor FAUX = new FauxEditor();
private Integer currentRow;
public TableCellEditor getCellEditor() {
return FAUX;
}
/**
* @brief Constructs a JTable out of a prefix to search for in the
* configuration, and a TableModel to apply to the table.
*
* The TableModel has the list of column names, and when you add
* "prefix." to the front of them, it makes a configuration entry
* which says the size of that column.
*
* Returns a new JTable, properly spaced and filled out according to
* the configuration preferences.
*
* @param name - A string that gets prepended to the column name
* in order to produce a display property showing
* the preferred width of that column.
* @param atm - A TableModel that will be used for rendering the table.
*/
public AuctionTable(String name, TableModel atm) {
super();
MQFactory.getConcrete("update " + name).addListener(this);
createDefaultRenderers();
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));
setDoubleBuffered(true);
setAutoCreateColumnsFromModel(false);
setModel(atm);
loadColumnSettings(name, atm);
doLayout();
}
public String getToolTipText(MouseEvent event) {
Point point = new Point(event.getX(), event.getY());
int rowPoint = rowAtPoint(point);
String result = null;
int colPoint = columnAtPoint(point);
if(rowPoint != -1) {
AuctionEntry ae = (AuctionEntry) getValueAt(rowPoint, -1);
boolean showThumbnail = true;
if(getRowHeight() == Constants.MICROTHUMBNAIL_ROW_HEIGHT) {
showThumbnail = getColumnName(colPoint).equals("Thumbnail");
}
result = ae.getPresenter().buildComment(showThumbnail);
}
return result == null ? super.getToolTipText(event) : result;
}
public void messageAction(Object deQ) {
int start = ((String)deQ).lastIndexOf(' ');
final String identifier = ((String)deQ).substring(start+1);
String command = ((String)deQ).substring(0, start);
int activeRow = currentRow == null ? 0 : currentRow.intValue();
if(command.equals("start")) {
currentRow = ((auctionTableModel)getModel()).findRow(new Comparison() {
public boolean match(Object o) {
String foundIdentifier = null;
if(o != null) foundIdentifier = ((AuctionEntry)o).getIdentifier();
return foundIdentifier != null && foundIdentifier.equals(identifier);
}
});
if(currentRow == -1) currentRow = null;
} else {
currentRow = null;
}
activeRow = currentRow == null ? activeRow : currentRow.intValue();
tableChanged(new TableModelEvent(getModel(), activeRow, activeRow, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
}
static class MouseListenerSelectProxy implements MouseListener {
private MouseListener m_peer;
MouseListenerSelectProxy(MouseListener ml) { m_peer = ml; }
public MouseListener getPeer() { return m_peer; }
/**
* Invoked when the mouse button has been clicked (pressed
* and released) on a component.
*/
public void mouseClicked(MouseEvent e) { if (!e.isPopupTrigger()) m_peer.mouseClicked(e); }
/**
* Invoked when the mouse enters a component.
*/
public void mouseEntered(MouseEvent e) { m_peer.mouseEntered(e); }
/**
* Invoked when the mouse exits a component.
*/
public void mouseExited(MouseEvent e) { m_peer.mouseExited(e); }
/**
* Invoked when a mouse button has been pressed on a component.
*/
public void mousePressed(MouseEvent e) { if (!e.isPopupTrigger()) m_peer.mousePressed(e); }
/**
* Invoked when a mouse button has been released on a component.
*/
public void mouseReleased(MouseEvent e) { if (!e.isPopupTrigger()) m_peer.mouseReleased(e); }
}
private final static String METAL_MOUSE_LISTENER = "javax.swing.plaf.basic.BasicTableUI$MouseInputHandler";
private final static String AQUA_MOUSE_LISTENER = "apple.laf.AquaTableUI$MouseInputHandler";
/** @noinspection InstanceVariableMayNotBeInitialized*/
// DO NOT INITIALIZE proxyMouseListener!!!!!
// It is used in addMouseListener BEFORE the constuctor is run
// (called from the base class constructor). If it is set here
// or in the constructor, it will clobber the value set by the
// call from the base class constructor.
private MouseListenerSelectProxy proxyMouseListener;
public synchronized void addMouseListener(MouseListener ml) {
String mlClass = ml.getClass().getName();
if ((proxyMouseListener == null) &&
(mlClass.equals(METAL_MOUSE_LISTENER) ||
mlClass.equals(AQUA_MOUSE_LISTENER))) {
proxyMouseListener = new MouseListenerSelectProxy(ml);
super.addMouseListener(proxyMouseListener);
} else {
super.addMouseListener(ml);
}
}
public synchronized void removeMouseListener(MouseListener ml) {
//noinspection ObjectEquality
if ((proxyMouseListener != null) && (ml == proxyMouseListener.getPeer())) {
super.removeMouseListener(proxyMouseListener);
proxyMouseListener = null;
} else {
super.removeMouseListener(ml);
}
}
private static final String[][] DEFAULT_COLUMNS = new String[][]{
{"Thumbnail", "100"},
{"Current", "89"},
{"Max", "62"},
{"Description", "297"},
{"Time left", "127"},
{"Status", "67"},
{"Seller", "147"},
};
private static int notify_delay = 0;
private class ColumnIndex extends Pair<String,Integer> {
public ColumnIndex(String s, Integer i) { super(s, i); }
}
private void loadColumnSettings(String prefix, TableModel atm) {
String curColumnName = "";
int columnCount = 0;
List<ColumnIndex> initialToSaved = new LinkedList<ColumnIndex>();
// This code would need to be somewhat revamped if we allowed
// arbitrary, or user-selected column names.
try {
for(int i = 0; i<atm.getColumnCount(); i++) {
curColumnName = atm.getColumnName(i);
//noinspection StringContatenationInLoop
String colWidth = JConfig.queryDisplayProperty(prefix + '.' + curColumnName);
if(colWidth == null) {
colWidth = JConfig.queryDisplayProperty(curColumnName);
}
if(colWidth != null) {
int dotIndex = colWidth.indexOf('.');
if (dotIndex != -1) {
String colIndex = colWidth.substring(0, dotIndex);
colWidth = colWidth.substring(dotIndex + 1);
initialToSaved.add(new ColumnIndex(curColumnName, Integer.parseInt(colIndex)));
}
columnCount++;
makeNewColumn(curColumnName, colWidth);
}
}
} catch(Exception e) {
// If we encountered any errors in earlier columns, don't try
// to set later columns.
JConfig.log().handleException("In display configuration for table " + prefix +", column \"" + curColumnName + "\" has an invalid property.", e);
JConfig.log().logDebug("No longer loading column widths from configuration.");
}
// If there are less than 2 columns, freak out and refresh.
if(columnCount < 2) {
SuperQueue.getInstance().preQueue("NOTIFY Column data for '" + prefix + "' was corrupted; resetting to defaults", "Swing", System.currentTimeMillis() + Constants.ONE_SECOND * 12 + notify_delay);
JConfig.log().logMessage("Column data for '" + prefix + "' was corrupted; resetting to defaults");
notify_delay += 2 * Constants.ONE_SECOND;
for(String[] column : DEFAULT_COLUMNS) {
String column_name = column[0];
String column_width = column[1];
if (column_name.equals("Time left") && prefix.equals("complete")) continue;
makeNewColumn(column_name, column_width);
}
}
if(!initialToSaved.isEmpty()) {
Collections.sort(initialToSaved, new Comparator<ColumnIndex>() {
public int compare(ColumnIndex o1, ColumnIndex o2) {
if(o1.getLast() < o2.getLast()) return -1;
if(o1.getLast() > o2.getLast()) return 1;
return 0;
}
});
for (ColumnIndex pair : initialToSaved) {
int colFrom = getColumnModel().getColumnIndex(pair.getFirst());
int colTo = pair.getLast();
try {
moveColumn(colFrom, colTo);
} catch (IllegalArgumentException iae) {
// Ignore it, and move on.
}
}
}
}
private void makeNewColumn(String curColumnName, String colWidth) {
TableColumn tc = new TableColumn(TableColumnController.getInstance().getColumnNumber(curColumnName));
tc.setHeaderValue(curColumnName);
tc.setIdentifier(curColumnName);
// Do not allow thumbnail columns to shrink below 75 pixels.
if ("Thumbnail".equals(curColumnName)) tc.setMinWidth(75);
addColumn(tc);
getColumn(curColumnName).setPreferredWidth(Integer.parseInt(colWidth));
getColumn(curColumnName).setWidth(Integer.parseInt(colWidth));
}
private boolean isCurrentRow(int i) {
return currentRow != null && currentRow.intValue() == i;
}
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if(isCurrentRow(row)) {
JComponent jc = (JComponent) c;
int left = column == 0 ? 1 : 0;
int right = column == getColumnCount() - 1 ? 1 : 0;
// TODO(cyberfox) Cache borders so only 3*(2^4) borders are created (4 binary variables times three distinct background colors).
jc.setBorder(BorderFactory.createMatteBorder(1, left, 1, right, Color.RED));
}
return c;
}
}