package com.jbidwatcher.ui; /* * Copyright (c) 2000-2007, CyberFOX Software, Inc. All Rights Reserved. * * Developed by mrs (Morgan Schweers) */ import com.cyberfox.util.platform.Platform; import com.jbidwatcher.auction.*; import com.jbidwatcher.ui.table.auctionTableModel; import com.jbidwatcher.util.config.*; import com.jbidwatcher.util.Currency; import com.jbidwatcher.util.Constants; import com.jbidwatcher.util.queue.MQFactory; import com.jbidwatcher.ui.util.*; import com.jbidwatcher.ui.table.TableColumnController; import com.jbidwatcher.ui.table.CSVExporter; import com.jbidwatcher.ui.table.AuctionTable; import com.jbidwatcher.util.queue.PlainMessageQueue; import org.jetbrains.annotations.Nullable; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import java.awt.*; import java.awt.event.ActionListener; import java.awt.dnd.DropTarget; import java.io.IOException; import java.util.*; import java.util.List; public class AuctionsUIModel { private Auctions _dataModel; private JTable _table; private JScrollPane _scroller; /** @noinspection FieldCanBeLocal*/ private DropTarget[] _targets; /* This can't be local, otherwise it gets GC'ed, which is bad. */ private Color _bgColor; private JPrintable _print; private CSVExporter _export; private JPanel mPanel; private final myTableCellRenderer _myRenderer; private auctionTableModel model; private final TableRowSorter<TableModel> sorter; /** * @brief Construct a new UI model for a provided auction list. * @param newAuctionList - The auction list to use as a 'backing * store' for displaying lists of auctions. * @param tableContextMenu - The context menu to present for this table. * @param frameContextMenu - The context menu to present for whitespace outside the table. * @param cornerButtonListener - The button to sit above the scrollbar. */ public AuctionsUIModel(Auctions newAuctionList, myTableCellRenderer cellRenderer, MultiSnipeManager multiManager, JContext tableContextMenu, final JContext frameContextMenu, ActionListener cornerButtonListener) { _myRenderer = cellRenderer; _dataModel = newAuctionList; _targets = new DropTarget[2]; model = new auctionTableModel(multiManager, _dataModel.getList()); _table = new AuctionTable(_dataModel.getName(), model); if(newAuctionList.isCompleted()) { if(_table.convertColumnIndexToView(TableColumnController.END_DATE) == -1) { _table.addColumn(new TableColumn(TableColumnController.END_DATE, Constants.DEFAULT_COLUMN_WIDTH, _myRenderer, null)); } } if(JConfig.queryConfiguration("show_shipping", "false").equals("true")) { if(_table.convertColumnIndexToView(TableColumnController.SHIPPING_INSURANCE) == -1) { _table.addColumn(new TableColumn(TableColumnController.SHIPPING_INSURANCE)); } JConfig.killAll("show_shipping"); } // provide sufficient vertical height in the rows for micro-thumbnails list view adjustRowHeight(); AbstractTableModel sortedModel = new DelegatedTableModel(model); sorter = new TableRowSorter<TableModel>(sortedModel); _table.setRowSorter(sorter); _table.addMouseListener(tableContextMenu); if(Platform.isMac() || JConfig.queryConfiguration("ui.useCornerButton", "true").equals("true")) { _scroller = new JScrollPane(_table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); } else { _scroller = new JScrollPane(_table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); } // This is a button to manage the custom columns for the current tab. if(JConfig.queryConfiguration("ui.useCornerButton", "true").equals("true")) { JButton cornerButton = new JButton("*"); cornerButton.addActionListener(cornerButtonListener); _scroller.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, cornerButton); } _bgColor = UIManager.getColor("window"); _scroller.setViewport(new JViewport() { private Image image; { setBackground(_bgColor); addMouseListener(frameContextMenu); setView(_table); try { image = ImageIO.read(JConfig.getResource("/jbidwatch.jpg")); } catch (IOException e) { image = null; } } public void paintComponent(Graphics g) { super.paintComponent(g); if(image != null && _table.getRowCount() == 0) { int imageW = image.getWidth(null); int imageH = image.getHeight(null); Graphics2D g2d = (Graphics2D) g; AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); Composite oldComp = g2d.getComposite(); g2d.setComposite(comp); int xloc = getWidth()/2 - imageW/2 - 2; int yloc = getHeight()/2 - imageH/2 - 2; g2d.drawImage(image, xloc, yloc, this); g2d.setComposite(oldComp); } } }); JDropListener _dropEar; if(newAuctionList.isCompleted()) { _dropEar = new JDropListener(new TargetDrop()); } else { _dropEar = new JDropListener(new TargetDrop(_dataModel.getName(), new ImageDropResolver() { public void handle(String imgUrl, Point location) { int rowPoint = _table.rowAtPoint(location); AuctionEntry whichAuction = (AuctionEntry)_table.getValueAt(rowPoint, -1); DeletedEntry.deleteThumbnails(whichAuction.getIdentifier()); whichAuction.getAuction().setThumbnailURL(imgUrl); ((PlainMessageQueue)MQFactory.getConcrete("thumbnail")).enqueueObject(whichAuction.getAuction()); } })); } _targets[0] = new DropTarget(_scroller.getViewport(), _dropEar); _targets[1] = new DropTarget(_table, _dropEar); _targets[0].setActive(true); _targets[1].setActive(true); _print = new JPrintable(_table); _export = new CSVExporter(_table); _table.setDefaultRenderer(String.class, _myRenderer); _table.setDefaultRenderer(Icon.class, _myRenderer); mPanel = new JPanel(); mPanel.setLayout(new BorderLayout()); mPanel.add(_scroller, BorderLayout.CENTER); addSumMonitor(_table); JPanel statusPanel = new TabStatusPanel(_dataModel.getName()); mPanel.add(statusPanel, BorderLayout.NORTH); } private void addSumMonitor(JTable table) { table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { updateSum(); } }); } private void updateSum() { int[] rowList = _table.getSelectedRows(); String total = sum(rowList); if(total == null) { MQFactory.getConcrete("Swing").enqueue("PRICE "); // A blank space to clear the price } else { MQFactory.getConcrete("Swing").enqueue("PRICE " + rowList.length + " / " + total); } } /** * @brief Pick and return a value from the entry that best describes * how much COULD be spent on it by the buyer. * * For an item not bid on, it's the current bid price. For an item * the user has bid on, it's their maximum bid. For an item the * user has a snipe set for, it's the maximum of their snipe bid. * If the item is closed, it's just the current bid price. * * @param checkEntry - The AuctionEntry to operate on. * * @return - A currency value containing either the current bid, the * users high bid, or the users snipe bid. */ private static Currency getBestBidValue(AuctionEntry checkEntry) { return checkEntry.bestValue(); } private static Currency addUSD(Currency inCurr, AuctionEntry ae) { boolean newCurrency = (inCurr == null || inCurr.isNull()); Currency currentUSPrice = ae.getCurrentUSPrice(); try { if(ae.getShippingWithInsurance().isNull()) { inCurr = newCurrency ? currentUSPrice : inCurr.add(currentUSPrice); } else { if (newCurrency) { inCurr = currentUSPrice.add(Currency.convertToUSD(currentUSPrice, ae.getCurrentPrice(), ae.getShippingWithInsurance())); } else { inCurr = inCurr.add(currentUSPrice.add(Currency.convertToUSD(currentUSPrice, ae.getCurrentPrice(), ae.getShippingWithInsurance()))); } } } catch(Currency.CurrencyTypeException cte) { JConfig.log().handleException("This should have been cleaned up.", cte); } return inCurr; } private static Currency addNonUSD(Currency inCurr, AuctionEntry ae) { boolean newCurrency = inCurr == null || inCurr.isNull(); try { if(ae.getShippingWithInsurance().isNull()) { inCurr = newCurrency ? getBestBidValue(ae) : inCurr.add(getBestBidValue(ae)); } else { if (newCurrency) { inCurr = getBestBidValue(ae).add(ae.getShippingWithInsurance()); } else { inCurr = inCurr.add(getBestBidValue(ae).add(ae.getShippingWithInsurance())); } } } catch(Currency.CurrencyTypeException cte) { JConfig.log().handleException("This should have been cleaned up.", cte); } return inCurr; } protected String sum(int[] rowList) { boolean approx = false, i18n = true; Currency accum = null; Currency withShipping = null; Currency withRealShipping = null; Currency realAccum = null; String result = null; try { for (int aRowList : rowList) { AuctionEntry entry; try { entry = (AuctionEntry) _table.getValueAt(aRowList, -1); Currency currentUSPrice = entry.getCurrentUSPrice(); if (accum == null) { accum = currentUSPrice; realAccum = getBestBidValue(entry); withShipping = addUSD(withShipping, entry); withRealShipping = addNonUSD(withRealShipping, entry); } else { if (!currentUSPrice.isNull() && !accum.isNull() && currentUSPrice.getCurrencyType() != Currency.NONE) { accum = accum.add(currentUSPrice); withShipping = addUSD(withShipping, entry); // If we're still trying to do the internationalization // thing, then try to keep track of the 'real' total. if (i18n) { //noinspection NestedTryStatement try { realAccum = realAccum.add(getBestBidValue(entry)); withRealShipping = addNonUSD(withRealShipping, entry); } catch (Currency.CurrencyTypeException cte) { // We can't handle multiple non-USD currency types, so // we stop trying to do the internationalization thing. i18n = false; } } } } if (entry.getCurrentPrice().getCurrencyType() != Currency.US_DOLLAR) approx = true; } catch (ClassCastException cce) { // Ignored } catch (IndexOutOfBoundsException bounds) { approx = true; } } result = formatSum(approx, i18n, accum, withShipping, withRealShipping, realAccum); } catch(Currency.CurrencyTypeException e) { JConfig.log().handleException("Sum currency exception!", e); } catch(ArrayIndexOutOfBoundsException ignored) { JConfig.log().logDebug("Selection of " + rowList.length + " items changed out from under 'sum'."); } catch(NullPointerException npe) { JConfig.log().logDebug("sum got NPE - this is common during delete operations"); } catch(Exception e) { JConfig.log().handleException("Sum serious exception!", e); } return result; } @Nullable private String formatSum(boolean approx, boolean i18n, Currency accum, Currency withShipping, Currency withRealShipping, Currency realAccum) { if (accum != null && !accum.isNull()) { String sAndH = "s/h"; if (!Locale.getDefault().equals(Locale.US)) sAndH = "p/p"; StringBuilder result = new StringBuilder(); // If we managed to do the i18n thing through it all, and we have // some real values, return it. if (i18n && realAccum != null) { result.append(realAccum.toString()); if (withRealShipping != null && !realAccum.equals(withRealShipping)) { result.append(" (").append(withRealShipping).append(" with ").append(sAndH).append(')'); } } else { if (approx) { if (withShipping != null && !accum.equals(withShipping)) { result.append("About "); formatAmountAndShipping(accum, withShipping, sAndH, result); } else { result.append("About ").append(accum.toString()); } } else { if (withShipping != null && !accum.equals(withShipping)) { formatAmountAndShipping(accum, withShipping, sAndH, result); } else { result.append(accum.toString()); } } } return result.toString(); } return null; } private void formatAmountAndShipping(Currency accum, Currency withShipping, String sAndH, StringBuilder result) { result.append(accum.toString()) .append(" (") .append(withShipping) .append(" with ") .append(sAndH) .append(')'); } public JPanel getPanel() { return mPanel; } public TableRowSorter<TableModel> getTableSorter() { return sorter; } public JTable getTable() { return _table; } /** * @brief Sets the background color for this tab to the passed in color. * * @param bgColor - The color to set the background to. */ public void setBackground(Color bgColor) { _scroller.getViewport().setBackground(bgColor); _table.setBackground(bgColor); _bgColor = bgColor; } /** * @brief Return the background color this was set to. * * @return - The color, if any, this tab was set to. */ public Color getBackground() { return _bgColor; } /** * Delete an auction entry, using that auction entry to match against. * This also tells the auction entry to unregister itself! * * @param inEntry - The auction entry to delete. */ public void delEntry(EntryInterface inEntry) { model.delete(inEntry); } /** * Add an AuctionEntry that has already been created, denying * duplicates, but allowing duplicates where both have useful * information that is not the same. * * @param aeNew - The new auction entry to add to the tables. */ public void addEntry(EntryInterface aeNew) { if (aeNew != null && model.insert(aeNew) == -1) { JConfig.log().logMessage("JBidWatch: Bad auction entry, cannot add!"); } } public boolean toggleField(String field) { boolean rval; int modelColumn = TableColumnController.getInstance().getColumnNumber(field); if(_table.convertColumnIndexToView(modelColumn) == -1) { TableColumn newColumn = new TableColumn(modelColumn, Constants.DEFAULT_COLUMN_WIDTH, _myRenderer, null); if(modelColumn == TableColumnController.THUMBNAIL) newColumn.setMinWidth(75); _table.addColumn(newColumn); rval = true; } else { _table.removeColumn(_table.getColumn(field)); rval = false; } adjustRowHeight(); return rval; } // hack and a half - but adding a row height attribute for columns seems like overkill public void adjustRowHeight() { Font def = _myRenderer.getDefaultFont(); Graphics g = _table.getGraphics(); int defaultHeight; if(def == null || g == null) { defaultHeight = Constants.DEFAULT_ROW_HEIGHT; } else { FontMetrics metrics = g.getFontMetrics(def); defaultHeight = metrics.getMaxAscent() + metrics.getMaxDescent() + metrics.getLeading()+4; } int thumbnailIndex = _table.convertColumnIndexToView(TableColumnController.THUMBNAIL); if (thumbnailIndex != -1) { defaultHeight = Math.max(Constants.MICROTHUMBNAIL_ROW_HEIGHT, defaultHeight); } _table.setRowHeight(Math.max(defaultHeight, Constants.DEFAULT_ROW_HEIGHT)); if(def != null) { _table.getTableHeader().setFont(def); } } public List<String> getColumns() { ArrayList<String> al = new ArrayList<String>(); for(int i = 0; i<_table.getColumnCount(); i++) { al.add(_table.getColumnName(i)); } return al; } public boolean export(String fname) { return _export.export(fname); } /** * @brief Print this table. * */ public void print() { _print.doPrint(); } /** * @brief Convert current column widths into display properties to * be saved for a future session. * * @param addToProps - The properties object to add the column widths to. * @param name - The category name to get the info from. */ public void getColumnWidthsToProperties(Properties addToProps, String name) { for(int j = 0; j<_table.getColumnCount(); j++) { TableColumn ct; try { ct = _table.getColumn(_table.getColumnName(j)); } catch(IllegalArgumentException iae) { JConfig.log().logMessage("Column can't be retrieved from the table: " + _table.getColumnName(j)); ct = null; } // ColumnProps cp = new ColumnProps(_dataModel.getColumnName(j), j, ct.getWidth()); //noinspection StringContatenationInLoop if(ct != null) addToProps.setProperty(name + '.' + _table.getColumnName(j), Integer.toString(j) + '.' + Integer.toString(ct.getWidth())); } } public void getColumnWidthsToProperties(Properties addToProps) { getColumnWidthsToProperties(addToProps, _dataModel.getName()); } public void sort() { sorter.sort(); } public void redrawAll() { _table.tableChanged(new TableModelEvent(model)); } private class DelegatedTableModel extends AbstractTableModel { private final auctionTableModel delegate; public DelegatedTableModel(auctionTableModel model) { delegate = model; } @Override public int getRowCount() {return delegate.getRowCount();} @Override public int getColumnCount() {return delegate.getColumnCount();} @Override public String getColumnName(int columnIndex) {return delegate.getColumnName(columnIndex);} @Override public Class<?> getColumnClass(int columnIndex) {return delegate.getSortByColumnClass(columnIndex);} @Override public boolean isCellEditable(int rowIndex, int columnIndex) {return delegate.isCellEditable(rowIndex, columnIndex);} @Override public Object getValueAt(int rowIndex, int columnIndex) {return delegate.getSortByValueAt(rowIndex, columnIndex);} @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) {delegate.setValueAt(aValue, rowIndex, columnIndex);} @Override public void addTableModelListener(TableModelListener l) {delegate.addTableModelListener(l);} @Override public void removeTableModelListener(TableModelListener l) {delegate.removeTableModelListener(l);} } }