package magic.ui.widget.deck.legality; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import magic.data.MagicFormat; import magic.data.MagicIcon; import magic.model.MagicCardDefinition; import magic.model.MagicDeck; import magic.translate.MText; import magic.translate.StringContext; import magic.ui.FontsAndBorders; import magic.ui.MagicImages; import magic.ui.screen.decks.ICardsTableListener; import magic.ui.widget.M.MScrollPane; import net.miginfocom.swing.MigLayout; @SuppressWarnings("serial") public class CardsLegalityPanel extends JPanel { // translatable strings private static final String _S1 = "Deck"; @StringContext(eg="Memnite <b>is illegal/banned/restricted</b> in Standard.") private static final String _S2 ="%s <b>%s</b> in %s."; @StringContext(eg="Illegal deck size for Vintage.") private static final String _S3 = "Illegal deck size for %s."; private static final String _S4 = "A minimum of %d cards is required."; // fired when selection changes. public static final String CP_CARD_SELECTED = "019f0246-bd63-4efd-a7cf-fefabea053e3"; private static final Color GRID_COLOR = new Color(194, 197, 203); private static final int ROW_HEIGHT = 23; //pixels private final MigLayout migLayout = new MigLayout(); private final MScrollPane scrollpane = new MScrollPane(); private final CardsLegalityTableModel tableModel; private final JTable table; private boolean isAdjusting = false; private int lastSelectedRow = -1; private final JLabel titleLabel; private MagicFormat magicFormat; private final List<ICardsTableListener> listeners = new ArrayList<>(); public CardsLegalityPanel() { this.tableModel = new CardsLegalityTableModel(); this.table = new JTable(tableModel) { private final Color defaultForeColor = getForeground(); @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component c = super.prepareRenderer(renderer, row, column); final CardLegalityInfo cardLegality = tableModel.getCardLegality(row); final boolean isRowSelected = table.isRowSelected(row); if (isRowSelected) { c.setForeground(cardLegality.isCardLegal() ? table.getSelectionForeground() : Color.LIGHT_GRAY); } else { if (cardLegality.isCardLegal()) { c.setForeground(defaultForeColor); } else { c.setForeground(Color.GRAY); } } return c; } }; table.setDefaultRenderer(Object.class, new HideCellFocusRenderer()); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setRowHeight(ROW_HEIGHT); table.setGridColor(GRID_COLOR); table.setTableHeader(null); table.getSelectionModel().addListSelectionListener(getTableListSelectionListener()); table.addMouseListener(getTableMouseAdapter()); table.setShowVerticalLines(false); final TableColumnModel model = table.getColumnModel(); setColumnWidths(model); // special renderer for legality indicator icon. model.getColumn(0).setCellRenderer(new LegalityCellRenderer()); // add table to scroll pane scrollpane.setViewportView(table); scrollpane.setBorder(FontsAndBorders.NO_BORDER); scrollpane.setBackground(Color.WHITE); titleLabel = new JLabel(MText.get(_S1)); titleLabel.setFont(getFont().deriveFont(Font.BOLD)); titleLabel.setHorizontalAlignment(SwingConstants.CENTER); titleLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.DARK_GRAY)); setLayout(migLayout); refreshLayout(); table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); } private ListSelectionListener getTableListSelectionListener() { return new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { isAdjusting = e.getValueIsAdjusting(); if (!isAdjusting) { lastSelectedRow = table.getSelectedRow(); notifyCardSelected(tableModel.getCardDef(lastSelectedRow)); firePropertyChange(CP_CARD_SELECTED, false, true); } } }; } private void notifyOnLeftClick(MagicCardDefinition card) { for (ICardsTableListener listener : listeners) { SwingUtilities.invokeLater(() -> { listener.onLeftClick(card); }); } } private void notifyOnRightClick(MagicCardDefinition card) { for (ICardsTableListener listener : listeners) { SwingUtilities.invokeLater(() -> { listener.onRightClick(card); }); } } private MouseAdapter getTableMouseAdapter() { return new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (!isAdjusting) { int mouseRow = table.rowAtPoint(e.getPoint()); MagicCardDefinition card = tableModel.getCardDef(mouseRow); card = card == null ? MagicCardDefinition.UNKNOWN : card; if (SwingUtilities.isLeftMouseButton(e)) { notifyOnLeftClick(card); } else if (SwingUtilities.isRightMouseButton(e)) { notifyOnRightClick(card); } } } }; } private void refreshLayout() { removeAll(); migLayout.setLayoutConstraints("flowy, insets 0, gap 0"); add(titleLabel, "w 100%, h 21!, hidemode 3"); add(scrollpane.component(), "w 100%, h 100%"); } private void setColumnWidths(final TableColumnModel model) { for (int i = 0; i < model.getColumnCount(); i++) { model.getColumn(i).setMinWidth(CardsLegalityTableModel.COLUMN_MIN_WIDTHS[i]); model.getColumn(i).setPreferredWidth(CardsLegalityTableModel.COLUMN_MIN_WIDTHS[i]); } model.getColumn(0).setMaxWidth(CardsLegalityTableModel.COLUMN_MIN_WIDTHS[0]); } public MagicCardDefinition getSelectedCard() { if (table.getSelectedRow() >= 0) { return tableModel.getCardDef(table.getSelectedRow()); } else { return MagicCardDefinition.UNKNOWN; } } public void setDeck(final MagicDeck aDeck, final MagicFormat aFormat) { this.magicFormat = aFormat; final boolean isValidDeckSize = aDeck.size() >= aFormat.getMinimumDeckSize(); scrollpane.setVisible(isValidDeckSize); if (isValidDeckSize) { tableModel.showDeckLegality(aDeck, aFormat); table.revalidate(); table.repaint(); setSelectedRow(); } else { repaint(); } } private void setSelectedRow() { if (lastSelectedRow >= 0 && lastSelectedRow < table.getRowCount()) { table.setRowSelectionInterval(lastSelectedRow, lastSelectedRow); } else { table.setRowSelectionInterval(0, 0); } } private class HideCellFocusRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); setBorder(noFocusBorder); return this; } } private static class LegalityCellRenderer extends DefaultTableCellRenderer { private static final JLabel BANNED_ICON = new JLabel(MagicImages.getIcon(MagicIcon.BANNED)); private static final JLabel RESTRICTED_ICON = new JLabel(MagicImages.getIcon(MagicIcon.RESTRICTED)); private static final JLabel LEGAL_ICON = new JLabel(MagicImages.getIcon(MagicIcon.LEGAL)); private static final JLabel ILLEGAL_ICON = new JLabel(MagicImages.getIcon(MagicIcon.ILLEGAL)); private JLabel getLegalityIcon(final CardLegalityInfo dfl) { switch (dfl.getLegality()) { case Banned: return BANNED_ICON; case Illegal: return ILLEGAL_ICON; case Legal: return LEGAL_ICON; case Restricted: return RESTRICTED_ICON; case TooManyCopies: return RESTRICTED_ICON; } throw new RuntimeException("Legality icon not found."); } @Override public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int col) { final CardsLegalityTableModel tableModel = (CardsLegalityTableModel) table.getModel(); final CardLegalityInfo cardLegality = tableModel.getCardLegality(row); final JLabel lbl = getLegalityIcon(cardLegality); lbl.setToolTipText(String.format("<html>%s</html>", MText.get(_S2, cardLegality.getCardName(), cardLegality.getLegality().getDescription(), cardLegality.getFormat().getName())) ); // match border and background formatting with default final JComponent defaultRender = (JComponent) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); lbl.setOpaque(defaultRender.isOpaque()); lbl.setBorder(defaultRender.getBorder()); if (isSelected) { lbl.setForeground(table.getSelectionForeground()); lbl.setBackground(table.getSelectionBackground()); } else { lbl.setForeground(getForeground()); // We have to create a new color object because Nimbus returns // a color of type DerivedColor, which behaves strange, not sure // why. lbl.setBackground(new Color(getBackground().getRed(), getBackground().getGreen(), getBackground().getBlue())); } lbl.setBorder(noFocusBorder); return lbl; } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (magicFormat != null) { final Graphics2D g2d = (Graphics2D)g.create(); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setFont(FontsAndBorders.FONT2); g2d.drawString(MText.get(_S3, magicFormat.getName()), 20, 60); g2d.drawString(MText.get(_S4, magicFormat.getMinimumDeckSize()), 20, 84); g2d.dispose(); } } private void notifyCardSelected(MagicCardDefinition card) { for (ICardsTableListener listener : listeners) { SwingUtilities.invokeLater(() -> { listener.onCardSelected(card == null ? MagicCardDefinition.UNKNOWN : card); }); } } void setCardsTableListeners(ICardsTableListener[] listeners) { this.listeners.clear(); this.listeners.addAll(Arrays.asList(listeners)); } }