package mage.client.cards;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.swing.*;
import mage.cards.Card;
import mage.cards.MageCard;
import mage.cards.decks.DeckCardInfo;
import mage.cards.decks.DeckCardLayout;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.client.MageFrame;
import mage.client.constants.Constants;
import mage.client.dialog.PreferencesDialog;
import mage.client.plugins.impl.Plugins;
import mage.client.util.*;
import mage.constants.CardType;
import mage.constants.SuperType;
import mage.util.RandomUtil;
import mage.view.CardView;
import mage.view.CardsView;
import org.apache.log4j.Logger;
import org.mage.card.arcane.CardRenderer;
/**
* Created by StravantUser on 2016-09-20.
*/
public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarget {
private final static Logger LOGGER = Logger.getLogger(DragCardGrid.class);
private Constants.DeckEditorMode mode;
@Override
public Collection<CardView> dragCardList() {
ArrayList<CardView> selectedCards = new ArrayList<>();
for (CardView card : allCards) {
if (card.isSelected()) {
selectedCards.add(card);
}
}
return selectedCards;
}
@Override
public void dragCardBegin() {
}
@Override
public void dragCardEnd(DragCardTarget target) {
if (target == this) {
// Already handled by dragged onto handler
} else if (target == null) {
// Don't remove the cards, no target
} else {
// Remove dragged cards
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i);
if (card.isSelected()) {
stack.set(i, null);
removeCardView(card);
eventSource.removeSpecificCard(card, "remove-specific-card");
}
}
}
}
trimGrid();
layoutGrid();
cardScroll.revalidate();
cardScroll.repaint();
}
}
@Override
public void dragCardEnter(MouseEvent e) {
insertArrow.setVisible(true);
}
@Override
public void dragCardMove(MouseEvent e) {
e = SwingUtilities.convertMouseEvent(this, e, cardContent);
showDropPosition(e.getX(), e.getY());
}
private void showDropPosition(int x, int y) {
// Clamp to region
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
// Determine column
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING);
int col = x / (cardWidth + GRID_PADDING);
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
if (dx < GRID_PADDING && col < gridWidth) {
// Which row to add to?
int curY = COUNT_LABEL_HEIGHT;
int rowIndex = 0;
for (int i = 0; i < cardGrid.size(); ++i) {
int maxStack = maxStackSize.get(i);
int rowHeight = cardTopHeight * (maxStack - 1) + cardHeight;
int rowBottom = curY + rowHeight + COUNT_LABEL_HEIGHT;
// Break out if we're in that row
if (y < rowBottom) {
// Set the row
rowIndex = i;
break;
} else {
rowIndex = i + 1;
curY = rowBottom;
}
}
// Insert between two columns
insertArrow.setIcon(INSERT_COL_ICON);
insertArrow.setSize(64, 64);
insertArrow.setLocation((cardWidth + GRID_PADDING) * col + GRID_PADDING / 2 - 32, curY);
} else {
// Clamp to a new col one after the current last one
col = Math.min(col, gridWidth);
// Determine place in the col
int curY = COUNT_LABEL_HEIGHT;
int rowIndex = 0;
int offsetIntoStack = 0;
for (int i = 0; i < cardGrid.size(); ++i) {
int maxStack = maxStackSize.get(i);
int rowHeight = cardTopHeight * (maxStack - 1) + cardHeight;
int rowBottom = curY + rowHeight + COUNT_LABEL_HEIGHT;
// Break out if we're in that row
if (y < rowBottom) {
// Set the row
rowIndex = i;
offsetIntoStack = y - curY;
break;
} else {
rowIndex = i + 1;
offsetIntoStack = y - rowBottom;
curY = rowBottom;
}
}
// Get the appropirate stack
ArrayList<CardView> stack;
if (rowIndex < cardGrid.size() && col < cardGrid.get(0).size()) {
stack = cardGrid.get(rowIndex).get(col);
} else {
stack = new ArrayList<>();
}
// Figure out position in the stack based on the offsetIntoRow
int stackInsertIndex = (offsetIntoStack + cardTopHeight / 2) / cardTopHeight;
stackInsertIndex = Math.max(0, Math.min(stackInsertIndex, stack.size()));
// Position arrow
insertArrow.setIcon(INSERT_ROW_ICON);
insertArrow.setSize(64, 32);
insertArrow.setLocation((cardWidth + GRID_PADDING) * col + GRID_PADDING + cardWidth / 2 - 32, curY + stackInsertIndex * cardTopHeight - 32);
}
}
@Override
public void dragCardExit(MouseEvent e) {
insertArrow.setVisible(false);
}
@Override
public void dragCardDrop(MouseEvent e, DragCardSource source, Collection<CardView> cards) {
e = SwingUtilities.convertMouseEvent(this, e, cardContent);
int x = e.getX();
int y = e.getY();
// Clamp to region
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
// If we're dragging onto ourself, erase the old cards (just null them out, we will
// compact the grid removing the null gaps / empty rows & cols later)
if (source == this) {
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (int i = 0; i < stack.size(); ++i) {
if (cards.contains(stack.get(i))) {
stack.set(i, null);
}
}
}
}
}
// Determine column
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING);
int col = x / (cardWidth + GRID_PADDING);
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
if (dx < GRID_PADDING && col < gridWidth) {
// Which row to add to?
int curY = COUNT_LABEL_HEIGHT;
int rowIndex = 0;
for (int i = 0; i < cardGrid.size(); ++i) {
int maxStack = maxStackSize.get(i);
int rowHeight = cardTopHeight * (maxStack - 1) + cardHeight;
int rowBottom = curY + rowHeight + COUNT_LABEL_HEIGHT;
// Break out if we're in that row
if (y < rowBottom) {
// Set the row
rowIndex = i;
break;
} else {
rowIndex = i + 1;
curY = rowBottom;
}
}
// Add a new row if needed
if (rowIndex >= cardGrid.size()) {
ArrayList<ArrayList<CardView>> newRow = new ArrayList<>();
if (!cardGrid.isEmpty()) {
for (int colIndex = 0; colIndex < cardGrid.get(0).size(); ++colIndex) {
newRow.add(new ArrayList<>());
}
}
cardGrid.add(newRow);
maxStackSize.add(0);
}
// Insert the new column to add to
for (int i = 0; i < cardGrid.size(); ++i) {
cardGrid.get(i).add(col, new ArrayList<>());
}
// Add the cards
cardGrid.get(rowIndex).get(col).addAll(cards);
} else {
// Clamp to a new col one after the current last one
col = Math.min(col, gridWidth);
// Determine place in the col
int curY = COUNT_LABEL_HEIGHT;
int rowIndex = 0;
int offsetIntoStack = 0;
for (int i = 0; i < cardGrid.size(); ++i) {
int maxStack = maxStackSize.get(i);
int rowHeight = cardTopHeight * (maxStack - 1) + cardHeight;
int rowBottom = curY + rowHeight + COUNT_LABEL_HEIGHT;
// Break out if we're in that row
if (y < rowBottom) {
// Set the row
rowIndex = i;
offsetIntoStack = y - curY;
break;
} else {
rowIndex = i + 1;
offsetIntoStack = y - rowBottom;
curY = rowBottom;
}
}
// Add a new row if needed
if (rowIndex >= cardGrid.size()) {
ArrayList<ArrayList<CardView>> newRow = new ArrayList<>();
if (!cardGrid.isEmpty()) {
for (int colIndex = 0; colIndex < cardGrid.get(0).size(); ++colIndex) {
newRow.add(new ArrayList<>());
}
}
cardGrid.add(newRow);
maxStackSize.add(0);
}
// Add a new col if needed
if (col >= cardGrid.get(0).size()) {
for (int i = 0; i < cardGrid.size(); ++i) {
cardGrid.get(i).add(new ArrayList<>());
}
}
// Get the appropirate stack
ArrayList<CardView> stack = cardGrid.get(rowIndex).get(col);
// Figure out position in the stack based on the offsetIntoRow
int stackInsertIndex = (offsetIntoStack + cardTopHeight / 2) / cardTopHeight;
stackInsertIndex = Math.max(0, Math.min(stackInsertIndex, stack.size()));
// Insert the cards
stack.addAll(stackInsertIndex, cards);
}
if (source == this) {
// Remove empty rows / cols / spaces in stacks
trimGrid();
layoutGrid();
cardScroll.revalidate();
cardScroll.repaint();
} else {
// Add new cards to grid
for (CardView card : cards) {
card.setSelected(true);
addCardView(card, false);
eventSource.addSpecificCard(card, "add-specific-card");
}
layoutGrid();
cardContent.repaint();
}
}
public void changeGUISize() {
layoutGrid();
cardScroll.getVerticalScrollBar().setUnitIncrement(CardRenderer.getCardTopHeight(getCardWidth()));
cardContent.repaint();
}
public void cleanUp() {
// Remove all of the cards from us
for (MageCard cardView : cardViews.values()) {
cardContent.remove(cardView);
}
// Clear out our tracking of stuff
cardGrid.clear();
maxStackSize.clear();
allCards.clear();
lastBigCard = null;
clearCardEventListeners();
}
public void addCardEventListener(Listener<mage.client.util.Event> listener) {
eventSource.addListener(listener);
}
public void clearCardEventListeners() {
eventSource.clearListeners();
}
public void setRole(Role role) {
this.role = role;
if (role == Role.SIDEBOARD) {
creatureCountLabel.setVisible(false);
landCountLabel.setVisible(false);
cardSizeSliderLabel.setVisible(false);
} else {
creatureCountLabel.setVisible(true);
landCountLabel.setVisible(true);
cardSizeSliderLabel.setVisible(true);
}
updateCounts();
}
public void removeSelection() {
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i);
if (card.isSelected()) {
eventSource.removeSpecificCard(card, "remove-specific-card");
stack.set(i, null);
removeCardView(card);
}
}
}
}
trimGrid();
layoutGrid();
cardContent.repaint();
}
public DeckCardLayout getCardLayout() {
// 2D Array to put entries into
java.util.List<java.util.List<java.util.List<DeckCardInfo>>> info = new ArrayList<>();
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
java.util.List<java.util.List<DeckCardInfo>> row = new ArrayList<>();
info.add(row);
for (ArrayList<CardView> stack : gridRow) {
row.add(stack.stream()
.map(card -> new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()))
.collect(Collectors.toList()));
}
}
// Store layout and settings then return them
return new DeckCardLayout(info, saveSettings().toString());
}
public void setDeckEditorMode(Constants.DeckEditorMode mode) {
this.mode = mode;
}
public enum Sort {
NONE("No Sort", (o1, o2) -> {
// Always equal, sort into the first row
return 0;
}),
CARD_TYPE("Card Type", new CardViewCardTypeComparator()),
CMC("Converted Mana Cost", new CardViewCostComparator()),
COLOR("Color", new CardViewColorComparator()),
COLOR_IDENTITY("Color Identity", new CardViewColorIdentityComparator()),
RARITY("Rarity", new CardViewRarityComparator()),
EDH_POWER_LEVEL("EDH Power Level", new CardViewEDHPowerLevelComparator());
Sort(String text, Comparator<CardView> comparator) {
this.comparator = comparator;
this.text = text;
}
public Comparator<CardView> getComparator() {
return comparator;
}
public String getText() {
return text;
}
private final Comparator<CardView> comparator;
private final String text;
}
private abstract class CardTypeCounter {
protected abstract boolean is(CardView card);
int get() {
return count;
}
void add(CardView card) {
if (is(card)) {
++count;
}
}
void remove(CardView card) {
if (is(card)) {
--count;
}
}
private int count = 0;
}
// Counters we use
private final CardTypeCounter creatureCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isCreature();
}
};
private final CardTypeCounter landCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isLand();
}
};
private final CardTypeCounter artifactCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isArtifact();
}
};
private final CardTypeCounter enchantmentCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isEnchantment();
}
};
private final CardTypeCounter instantCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isInstant();
}
};
private final CardTypeCounter sorceryCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isSorcery();
}
};
private final CardTypeCounter planeswalkerCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isPlanesWalker();
}
};
private final CardTypeCounter tribalCounter = new CardTypeCounter() {
@Override
protected boolean is(CardView card) {
return card.isTribal();
}
};
private final CardTypeCounter[] allCounters = {
creatureCounter,
landCounter,
artifactCounter,
enchantmentCounter,
instantCounter,
planeswalkerCounter,
sorceryCounter,
tribalCounter
};
// Listener
public interface DragCardGridListener {
void cardsSelected();
void hideCards(Collection<CardView> card);
void duplicateCards(Collection<CardView> cards);
void invertCardSelection(Collection<CardView> cards);
void showAll();
}
// Constants
public static final int COUNT_LABEL_HEIGHT = 20;
public static final int GRID_PADDING = 10;
private final static ImageIcon INSERT_ROW_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_row.png"));
private final static ImageIcon INSERT_COL_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_col.png"));
// All of the current card views
private final Map<UUID, MageCard> cardViews = new LinkedHashMap<>();
private final ArrayList<CardView> allCards = new ArrayList<>();
// Card listeners
private final CardEventSource eventSource = new CardEventSource();
// Last big card
BigCard lastBigCard = null;
// Top bar with dropdowns for sort / filter / etc
JButton sortButton;
JButton filterButton;
JButton visibilityButton;
JButton selectByButton;
JButton analyseButton;
JButton blingButton;
// Popup for toolbar
final JPopupMenu filterPopup;
JPopupMenu selectByTypePopup;
final JPopupMenu sortPopup;
final JPopupMenu selectByPopup;
final JCheckBox separateCreaturesCb;
final JTextField searchByTextField;
JToggleButton multiplesButton;
final JSlider cardSizeSlider;
final JLabel cardSizeSliderLabel;
final Map<Sort, AbstractButton> sortButtons = new HashMap<>();
final HashMap<CardType, AbstractButton> selectByTypeButtons = new HashMap<>();
final JLabel deckNameAndCountLabel;
final JLabel landCountLabel;
final JLabel creatureCountLabel;
// Main two controls holding the scrollable card grid
final JScrollPane cardScroll;
JLayeredPane cardContent;
// Drag onto insert arrow
final JLabel insertArrow;
// Card area selection panel
final SelectionBox selectionPanel;
Set<CardView> selectionDragStartCards;
int selectionDragStartX;
int selectionDragStartY;
// Card size mod
float cardSizeMod = 1.0f;
// The role (maindeck or sideboard)
Role role = Role.MAINDECK;
// Dragging
private final CardDraggerGlassPane dragger = new CardDraggerGlassPane(this);
// The grid of cards
// The outermost array contains multiple rows of stacks of cards
// The next inner array represents a row of stacks of cards
// The innermost array represents a single vertical stack of cards
private ArrayList<ArrayList<ArrayList<CardView>>> cardGrid;
private ArrayList<Integer> maxStackSize = new ArrayList<>();
private final ArrayList<ArrayList<JLabel>> stackCountLabels = new ArrayList<>();
private Sort cardSort = Sort.CMC;
private final ArrayList<CardType> selectByTypeSelected = new ArrayList<>();
private boolean separateCreatures = true;
public enum Role {
MAINDECK("Maindeck"),
SIDEBOARD("Sideboard");
Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
private final String name;
}
public static class Settings {
public Sort sort;
public boolean separateCreatures;
public int cardSize;
private final static Pattern parser = Pattern.compile("\\(([^,]*),([^,]*),([^)]*)\\)");
public static Settings parse(String str) {
Matcher m = parser.matcher(str);
if (m.find()) {
Settings s = new Settings();
if (m.groupCount() > 0) {
s.sort = Sort.valueOf(m.group(1));
}
if (m.groupCount() > 1) {
s.separateCreatures = Boolean.valueOf(m.group(2));
}
if (m.groupCount() > 2) {
s.cardSize = Integer.valueOf(m.group(3));
} else {
s.cardSize = 50;
}
return s;
} else {
return null;
}
}
@Override
public String toString() {
return '(' + sort.toString() + ',' + Boolean.toString(separateCreatures) + ',' + Integer.toString(cardSize) + ')';
}
}
public Settings saveSettings() {
Settings s = new Settings();
s.sort = cardSort;
s.separateCreatures = separateCreatures;
s.cardSize = cardSizeSlider.getValue();
return s;
}
public void loadSettings(Settings s) {
if (s != null) {
setSort(s.sort);
setSeparateCreatures(s.separateCreatures);
setCardSize(s.cardSize);
resort();
}
}
public void setSeparateCreatures(boolean state) {
separateCreatures = state;
separateCreaturesCb.setSelected(state);
}
public void setSort(Sort s) {
cardSort = s;
sortButtons.get(s).setSelected(true);
}
public void setCardSize(int size) {
cardSizeSlider.setValue(size);
}
// Constructor
public DragCardGrid() {
// Make sure that the card grid is populated with at least one (empty) stack to begin with
cardGrid = new ArrayList<>();
// Component init
setLayout(new BorderLayout());
setOpaque(false);
// Editting mode
this.mode = Constants.DeckEditorMode.LIMITED_BUILDING;
// Toolbar
sortButton = new JButton("Sort");
filterButton = new JButton("Filter");
visibilityButton = new JButton("V"); // "Visibility" button
selectByButton = new JButton("Select By");
analyseButton = new JButton("M"); // "Mana" button
blingButton = new JButton("B"); // "Bling" button
// Name and count label
deckNameAndCountLabel = new JLabel();
// Count labels
landCountLabel = new JLabel("", new ImageIcon(getClass().getResource("/buttons/type_land.png")), SwingConstants.LEFT);
landCountLabel.setToolTipText("Number of lands in deck");
creatureCountLabel = new JLabel("", new ImageIcon(getClass().getResource("/buttons/type_creatures.png")), SwingConstants.LEFT);
creatureCountLabel.setToolTipText("Number of creatures in deck");
JPanel toolbar = new JPanel(new BorderLayout());
JPanel toolbarInner = new JPanel();
toolbar.setBackground(new Color(250, 250, 250, 150));
toolbar.setOpaque(true);
toolbarInner.setOpaque(false);
toolbarInner.add(deckNameAndCountLabel);
toolbarInner.add(landCountLabel);
toolbarInner.add(creatureCountLabel);
toolbarInner.add(sortButton);
toolbarInner.add(filterButton);
toolbarInner.add(selectByButton);
toolbarInner.add(visibilityButton);
toolbarInner.add(analyseButton);
toolbarInner.add(blingButton);
toolbar.add(toolbarInner, BorderLayout.WEST);
JPanel sliderPanel = new JPanel(new GridBagLayout());
sliderPanel.setOpaque(false);
cardSizeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 50);
cardSizeSlider.setOpaque(false);
cardSizeSlider.setPreferredSize(new Dimension(100, (int) cardSizeSlider.getPreferredSize().getHeight()));
cardSizeSlider.addChangeListener(e -> {
if (!cardSizeSlider.getValueIsAdjusting()) {
// Fraction in [-1, 1]
float sliderFrac = ((float) (cardSizeSlider.getValue() - 50)) / 50;
// Convert to frac in [0.5, 2.0] exponentially
cardSizeMod = (float) Math.pow(2, sliderFrac);
// Update grid
layoutGrid();
cardContent.repaint();
}
});
cardSizeSliderLabel = new JLabel("Card Size:");
sliderPanel.add(cardSizeSliderLabel);
sliderPanel.add(cardSizeSlider);
toolbar.add(sliderPanel, BorderLayout.EAST);
this.add(toolbar, BorderLayout.NORTH);
// Content
cardContent = new JLayeredPane();
cardContent.setLayout(null);
cardContent.setOpaque(false);
cardContent.addMouseListener(new MouseAdapter() {
private boolean isDragging = false;
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
isDragging = true;
beginSelectionDrag(e.getX(), e.getY(), e.isShiftDown());
updateSelectionDrag(e.getX(), e.getY());
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (isDragging) {
isDragging = false;
updateSelectionDrag(e.getX(), e.getY());
endSelectionDrag(e.getX(), e.getY());
}
}
});
cardContent.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
updateSelectionDrag(e.getX(), e.getY());
}
});
cardScroll = new JScrollPane(cardContent,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
cardScroll.setOpaque(false);
cardScroll.getViewport().setOpaque(false);
cardScroll.setViewportBorder(BorderFactory.createEmptyBorder());
cardScroll.setBorder(BorderFactory.createLineBorder(Color.gray, 1));
cardScroll.getVerticalScrollBar().setUnitIncrement(CardRenderer.getCardTopHeight(getCardWidth()));
this.add(cardScroll, BorderLayout.CENTER);
// Insert arrow
insertArrow = new JLabel();
insertArrow.setSize(20, 20);
insertArrow.setVisible(false);
cardContent.add(insertArrow, new Integer(1000));
// Selection panel
selectionPanel = new SelectionBox();
selectionPanel.setVisible(false);
cardContent.add(selectionPanel, new Integer(1001));
// Load separate creatures setting
separateCreatures = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, "false").equals("true");
try {
cardSort = Sort.valueOf(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SORT, Sort.NONE.toString()));
} catch (IllegalArgumentException ex) {
cardSort = Sort.NONE;
}
// Sort popup
{
sortPopup = new JPopupMenu();
sortPopup.setLayout(new GridBagLayout());
JPanel sortMode = new JPanel();
sortMode.setLayout(new GridLayout(Sort.values().length, 1, 0, 2));
sortMode.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Sort by..."));
GridBagConstraints sortModeC = new GridBagConstraints();
sortModeC.gridx = 0;
sortModeC.gridy = 0;
sortModeC.gridwidth = 1;
sortModeC.gridheight = 1;
sortModeC.fill = GridBagConstraints.HORIZONTAL;
sortPopup.add(sortMode, sortModeC);
ButtonGroup sortModeGroup = new ButtonGroup();
for (final Sort s : Sort.values()) {
JToggleButton button = new JToggleButton(s.getText());
if (s == cardSort) {
button.setSelected(true);
}
sortButtons.put(s, button);
sortMode.add(button);
sortModeGroup.add(button);
button.addActionListener(e -> {
cardSort = s;
PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SORT, s.toString());
resort();
});
}
JPanel sortOptions = new JPanel();
sortOptions.setLayout(new BoxLayout(sortOptions, BoxLayout.Y_AXIS));
sortOptions.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Sort options"));
GridBagConstraints sortOptionsC = new GridBagConstraints();
sortOptionsC.gridx = 0;
sortOptionsC.gridy = 1;
sortOptionsC.gridwidth = 1;
sortOptionsC.gridheight = 1;
sortPopup.add(sortOptions, sortOptionsC);
separateCreaturesCb = new JCheckBox();
separateCreaturesCb.setText("Creatures in separate row");
separateCreaturesCb.setSelected(separateCreatures);
separateCreaturesCb.addItemListener(e -> {
setSeparateCreatures(separateCreaturesCb.isSelected());
PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, Boolean.toString(separateCreatures));
resort();
});
sortOptions.add(separateCreaturesCb);
sortPopup.pack();
makeButtonPopup(sortButton, sortPopup);
}
// Visibility popup
{
final JPopupMenu visPopup = new JPopupMenu();
JMenuItem hideSelected = new JMenuItem("Hide selected");
hideSelected.addActionListener(e -> hideSelection());
visPopup.add(hideSelected);
JMenuItem showAll = new JMenuItem("Show all");
showAll.addActionListener(e -> showAll());
visPopup.add(showAll);
visibilityButton.setToolTipText("Visibility of cards. Right click to get the same options this provides");
visibilityButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
visPopup.show(e.getComponent(), 0, e.getComponent().getHeight());
}
});
}
// selectBy.. popup
{
selectByPopup = new JPopupMenu();
selectByPopup.setLayout(new GridBagLayout());
JPanel selectByTypeMode = new JPanel();
selectByTypeMode.setLayout(new GridLayout(CardType.values().length, 1, 0, 2));
selectByTypeMode.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Select by Type"));
GridBagConstraints selectByTypeModeC = new GridBagConstraints();
selectByTypeModeC.gridx = 0;
selectByTypeModeC.gridy = 0;
selectByTypeModeC.gridwidth = 1;
selectByTypeModeC.gridheight = 1;
selectByTypeModeC.fill = GridBagConstraints.HORIZONTAL;
selectByPopup.add(selectByTypeMode, selectByTypeModeC);
ButtonGroup selectByTypeModeGroup = new ButtonGroup();
for (final CardType cardType : CardType.values()) {
if (cardType == cardType.CONSPIRACY) {
multiplesButton = new JToggleButton("Multiples");
selectByTypeButtons.put(cardType, multiplesButton);
selectByTypeMode.add(multiplesButton);
selectByTypeModeGroup.add(multiplesButton);
multiplesButton.addActionListener(e -> {
multiplesButton.setSelected(!multiplesButton.isSelected());
reselectBy();
});
continue;
}
JToggleButton button = new JToggleButton(cardType.toString());
selectByTypeButtons.put(cardType, button);
selectByTypeMode.add(button);
selectByTypeModeGroup.add(button);
button.addActionListener(e -> {
//selectByTypeSelected.add(cardType);
button.setSelected(!button.isSelected());
reselectBy();
});
}
JPanel selectBySearchPanel = new JPanel();
selectBySearchPanel.setPreferredSize(new Dimension(150, 60));
selectBySearchPanel.setLayout(new GridLayout(1, 1));
selectBySearchPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Search:"));
GridBagConstraints selectBySearchPanelC = new GridBagConstraints();
selectBySearchPanelC.gridx = 0;
selectBySearchPanelC.gridy = 1;
selectBySearchPanelC.gridwidth = 1;
selectBySearchPanelC.gridheight = 1;
selectBySearchPanelC.fill = GridBagConstraints.HORIZONTAL;
selectBySearchPanelC.fill = GridBagConstraints.VERTICAL;
searchByTextField = new JTextField();
searchByTextField.setToolTipText("Searches for card names, types, rarity, casting cost and rules text. NB: Mana symbols are written like {W},{U},{C} etc");
searchByTextField.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent e) {
reselectBy();
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
}
});
selectBySearchPanel.add(searchByTextField);
selectByPopup.add(selectBySearchPanel, selectBySearchPanelC);
makeButtonPopup(selectByButton, selectByPopup);
}
// Analyse Mana (aka #blue pips, #islands, #white pips, #plains etc.)
analyseButton.setToolTipText("Mana Analyser! Counts coloured/colourless mana costs. Counts land types.");
analyseButton.addActionListener(evt -> analyseDeck());
// Bling button - aka Add in a premium 'JR', 'MBP', 'CS' etc card
blingButton.setToolTipText("Bling your deck! Select the original and added cards by selecting 'Multiples' in the selection options");
blingButton.addActionListener(evt -> blingDeck());
// Filter popup
filterPopup = new JPopupMenu();
filterPopup.setPreferredSize(new Dimension(300, 300));
makeButtonPopup(filterButton, filterPopup);
filterButton.setVisible(false);
// Right click in card area
initCardAreaPopup();
// Update counts
updateCounts();
}
public void initCardAreaPopup() {
final JPopupMenu menu = new JPopupMenu();
final JMenuItem hideSelected = new JMenuItem("Hide selected");
hideSelected.addActionListener(e -> hideSelection());
menu.add(hideSelected);
JMenuItem showAll = new JMenuItem("Show all");
showAll.addActionListener(e -> showAll());
menu.add(showAll);
JMenu sortMenu = new JMenu("Sort by...");
final Map<Sort, JMenuItem> sortMenuItems = new LinkedHashMap<>();
for (final Sort sort : Sort.values()) {
JMenuItem subSort = new JCheckBoxMenuItem(sort.getText());
sortMenuItems.put(sort, subSort);
subSort.addActionListener(e -> {
cardSort = sort;
resort();
});
sortMenu.add(subSort);
}
sortMenu.add(new JPopupMenu.Separator());
final JCheckBoxMenuItem separateButton = new JCheckBoxMenuItem("Separate creatures");
separateButton.addActionListener(e -> {
setSeparateCreatures(!separateCreatures);
resort();
});
sortMenu.add(separateButton);
menu.add(sortMenu);
// Hook up to card content
cardContent.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
for (Sort s : sortMenuItems.keySet()) {
sortMenuItems.get(s).setSelected(cardSort == s);
}
hideSelected.setEnabled(!dragCardList().isEmpty());
separateButton.setSelected(separateCreatures);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
});
}
/**
* Deselect all cards in this DragCardGrid
*/
public void deselectAll() {
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (CardView card : stack) {
if (card.isSelected()) {
card.setSelected(false);
cardViews.get(card.getId()).update(card);
}
}
}
}
}
private void hideSelection() {
Collection<CardView> toHide = dragCardList();
for (DragCardGridListener l : listeners) {
l.hideCards(toHide);
}
}
private void duplicateSelection() {
Collection<CardView> toDuplicate = dragCardList();
for (DragCardGridListener l : listeners) {
l.duplicateCards(toDuplicate);
}
}
private void invertSelection() {
Collection<CardView> toInvert = allCards;
for (DragCardGridListener l : listeners) {
l.invertCardSelection(toInvert);
for (CardView card : allCards) {
MageCard view = cardViews.get(card.getId());
view.update(card);
}
}
repaint();
}
private void showAll() {
for (DragCardGridListener l : listeners) {
l.showAll();
}
}
/**
* Selection drag handling
*/
private void beginSelectionDrag(int x, int y, boolean shiftHeld) {
// Show the selection panel
selectionPanel.setVisible(true);
selectionPanel.setLocation(x, y);
cardScroll.revalidate();
// Store the drag start location
selectionDragStartX = x;
selectionDragStartY = y;
// Store the starting cards to include in the selection
selectionDragStartCards = new HashSet<>();
if (shiftHeld) {
selectionDragStartCards.addAll(dragCardList());
}
// Notify selection
notifyCardsSelected();
}
private void updateSelectionDrag(int x, int y) {
// Coords
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int x1 = Math.min(x, selectionDragStartX);
int x2 = Math.max(x, selectionDragStartX);
int y1 = Math.min(y, selectionDragStartY);
int y2 = Math.max(y, selectionDragStartY);
// Update selection panel size
selectionPanel.setLocation(x1, y1);
selectionPanel.setSize(x2 - x1, y2 - y1);
// First and last cols
int col1 = x1 / (cardWidth + GRID_PADDING);
int col2 = x2 / (cardWidth + GRID_PADDING);
int offsetIntoCol2 = x2 % (cardWidth + GRID_PADDING);
if (offsetIntoCol2 < GRID_PADDING) {
--col2;
}
// avoids a null ref issue but only deals with symptom of problem. not sure how it gets to this state ever. see issue #3197
// if (selectionDragStartCards == null) return;
int curY = COUNT_LABEL_HEIGHT;
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
int stackStartIndex;
if (y1 < curY) {
stackStartIndex = 0;
} else {
stackStartIndex = (y1 - curY) / cardTopHeight;
}
int stackEndIndex;
if (y2 < curY) {
stackEndIndex = -1;
} else {
stackEndIndex = (y2 - curY) / cardTopHeight;
}
ArrayList<ArrayList<CardView>> gridRow = cardGrid.get(rowIndex);
for (int col = 0; col < gridRow.size(); ++col) {
ArrayList<CardView> stack = gridRow.get(col);
int stackBottomBegin = curY + cardTopHeight * (stack.size());
int stackBottomEnd = curY + cardTopHeight * (stack.size() - 1) + cardHeight;
for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i);
MageCard view = cardViews.get(card.getId());
boolean inBoundsX = (col >= col1 && col <= col2);
boolean inBoundsY = (i >= stackStartIndex && i <= stackEndIndex);
boolean lastCard = (i == stack.size() - 1);
boolean inSeletionDrag = inBoundsX && (inBoundsY || (lastCard && (y2 >= stackBottomBegin && y1 <= stackBottomEnd)));
if (inSeletionDrag || selectionDragStartCards != null && selectionDragStartCards.contains(card)) {
if (!card.isSelected()) {
card.setSelected(true);
view.update(card);
}
} else if (card.isSelected()) {
card.setSelected(false);
view.update(card);
}
}
}
curY += cardTopHeight * (maxStackSize.get(rowIndex) - 1) + cardHeight + COUNT_LABEL_HEIGHT;
}
}
private void endSelectionDrag(@SuppressWarnings("unused") int x, @SuppressWarnings("unused") int y) {
// Hide the selection panel
selectionPanel.setVisible(false);
}
// Resort the existing cards based on the current sort
public void resort() {
// First null out the grid and trim it down
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
stack.clear();
}
}
trimGrid();
// First sort all cards by name
allCards.sort(new CardViewNameComparator());
// Now re-insert all of the cards using the current sort
for (CardView card : allCards) {
sortIntoGrid(card);
}
// Deselect everything
deselectAll();
// And finally rerender
layoutGrid();
repaint();
}
public void reselectBy() {
// Deselect everything
deselectAll();
boolean useText = false;
String searchStr = "";
if (searchByTextField.getText().length() >= 3) {
useText = true;
searchStr = searchByTextField.getText().toLowerCase();
}
for (CardType cardType : selectByTypeButtons.keySet()) {
AbstractButton button = selectByTypeButtons.get(cardType);
if (button != null) {
if (button.isSelected()) {
// Special case - "Multiples" (CONSPIRACY type)
if (cardType == CardType.CONSPIRACY) {
HashMap<String, CardView> cardNames = new HashMap<>();
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (CardView card : stack) {
if (cardNames.get(card.getName()) == null) {
cardNames.put(card.getName(), card);
} else {
card.setSelected(true);
cardViews.get(card.getId()).update(card);
CardView origCard = cardNames.get(card.getName());
origCard.setSelected(true);
cardViews.get(origCard.getId()).update(origCard);
}
}
}
}
continue;
}
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (CardView card : stack) {
boolean s = card.isSelected() | card.getCardTypes().contains(cardType);
card.setSelected(s);
cardViews.get(card.getId()).update(card);
}
}
}
}
}
}
if (useText) {
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (CardView card : stack) {
boolean s = card.isSelected();
// Name
if (!s) {
s |= card.getName().toLowerCase().contains(searchStr);
}
// Sub & Super Types
if (!s) {
for (SuperType str : card.getSuperTypes()) {
s |= str.toString().toLowerCase().contains(searchStr);
}
for (String str : card.getSubTypes()) {
s |= str.toLowerCase().contains(searchStr);
}
}
// Rarity
if (!s) {
s |= card.getRarity().toString().toLowerCase().contains(searchStr);
}
// Type line
if (!s) {
String t = "";
for (CardType type : card.getCardTypes()) {
t += ' ' + type.toString();
}
s |= t.toLowerCase().contains(searchStr);
}
// Casting cost
if (!s) {
String mc = "";
for (String m : card.getManaCost()) {
mc += m;
}
s |= mc.toLowerCase().contains(searchStr);
}
// Rules
if (!s) {
for (String str : card.getRules()) {
s |= str.toLowerCase().contains(searchStr);
}
}
card.setSelected(s);
cardViews.get(card.getId()).update(card);
}
}
}
}
// And finally rerender
layoutGrid();
repaint();
}
private static final Pattern pattern = Pattern.compile(".*Add(.*)(\\{[WUBRGXC]\\})(.*)to your mana pool");
public void analyseDeck() {
HashMap<String, Integer> qtys = new HashMap<>();
HashMap<String, Integer> pips = new HashMap<>();
HashMap<String, Integer> sourcePips = new HashMap<>();
HashMap<String, Integer> manaCounts = new HashMap<>();
pips.put("#w}", 0);
pips.put("#u}", 0);
pips.put("#b}", 0);
pips.put("#r}", 0);
pips.put("#g}", 0);
pips.put("#c}", 0);
qtys.put("plains", 0);
qtys.put("island", 0);
qtys.put("swamp", 0);
qtys.put("mountain", 0);
qtys.put("forest", 0);
qtys.put("basic", 0);
qtys.put("wastes", 0);
manaCounts = new HashMap<>();
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (CardView card : stack) {
// Type line
String t = "";
for (CardType type : card.getCardTypes()) {
t += ' ' + type.toString();
}
// Sub & Super Types
for (SuperType type : card.getSuperTypes()) {
t += ' ' + type.toString().toLowerCase();
}
for (String str : card.getSubTypes()) {
t += ' ' + str.toLowerCase();
}
for (String qty : qtys.keySet()) {
int value = qtys.get(qty);
if (t.toLowerCase().contains(qty)) {
qtys.put(qty, ++value);
}
// Rules
for (String str : card.getRules()) {
if (str.toLowerCase().contains(qty)) {
qtys.put(qty, ++value);
}
}
}
// Wastes (special case)
if (card.getName().equals("Wastes")) {
int value = qtys.get("wastes");
qtys.put("wastes", ++value);
}
// Mana Cost
String mc = "";
for (String m : card.getManaCost()) {
mc += m;
}
mc = mc.replaceAll("\\{([WUBRG]).([WUBRG])\\}", "{$1}{$2}");
mc = mc.replaceAll("\\{", "#");
mc = mc.toLowerCase();
for (String pip : pips.keySet()) {
int value = pips.get(pip);
while (mc.toLowerCase().contains(pip)) {
pips.put(pip, ++value);
mc = mc.replaceFirst(pip, "");
}
}
// Adding mana
for (String str : card.getRules()) {
Matcher m = pattern.matcher(str);
// ".*Add(.*)(\\{[WUBRGXC]\\})(.*)to your mana pool"
while (m.find()) {
System.out.println("0=" + m.group(0) + ",,,1=" + m.group(1) + ",,,2=" + m.group(2) + ",,,3=" + m.group(3));
str = "Add" + m.group(1) + m.group(3) + "to your mana pool";
int num = 1;
if (manaCounts.get(m.group(2)) != null) {
num = manaCounts.get(m.group(2));
num++;
}
manaCounts.put(m.group(2), num);
m = pattern.matcher(str);
}
}
}
}
}
String finalInfo = "Found the following quantity of mana costs, mana sources and land types:<br><font size=-1><ul>";
for (String qty : qtys.keySet()) {
int value = qtys.get(qty);
if (value > 0) {
finalInfo += "<li>" + qty + " = " + value;
}
}
for (String source : sourcePips.keySet()) {
int value = sourcePips.get(source);
if (value > 0) {
finalInfo += "<li>" + "Mana source " + source + " = " + value;
}
}
for (String pip : pips.keySet()) {
int value = pips.get(pip);
if (value > 0) {
finalInfo += "<li>" + pip.toUpperCase() + " mana pip/s = " + value;
}
}
for (String mana : manaCounts.keySet()) {
int value = manaCounts.get(mana);
if (value > 0) {
finalInfo += "<li>" + mana.toUpperCase() + " mana sources = " + value;
}
}
finalInfo = finalInfo.replaceAll("#", "\\{");
finalInfo += "</ul>";
MageFrame.getInstance().showMessage(finalInfo);
}
public void blingDeck() {
if (!(this.mode == Constants.DeckEditorMode.FREE_BUILDING)) {
return;
}
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to bling your deck? This process will add cards!", "WARNING",
JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
return;
}
HashMap<String, Integer> pimpedSets = new HashMap<>();
HashMap<CardView, Integer> pimpedCards = new HashMap<>();
pimpedSets.put("CP", 1);
pimpedSets.put("JR", 1);
pimpedSets.put("MPS", 1);
pimpedSets.put("CLASH", 1);
pimpedSets.put("ARENA", 1);
pimpedSets.put("UGIN", 1);
pimpedSets.put("WMCQ", 1);
pimpedSets.put("APAC", 1);
pimpedSets.put("EURO", 1);
pimpedSets.put("FNMP", 1);
pimpedSets.put("MGDC", 1);
pimpedSets.put("MPRP", 1);
pimpedSets.put("EXP", 1);
pimpedSets.put("GPX", 1);
pimpedSets.put("GRC", 1);
pimpedSets.put("MBP", 1);
pimpedSets.put("MLP", 1);
pimpedSets.put("PLS", 1);
pimpedSets.put("PTC", 1);
pimpedSets.put("SUS", 1);
String[] sets = pimpedSets.keySet().toArray(new String[pimpedSets.keySet().size()]);
Boolean didModify = false;
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (ArrayList<CardView> stack : gridRow) {
for (CardView card : stack) {
if (card.getSuperTypes().contains(SuperType.BASIC)) {
continue;
}
if (!pimpedSets.containsKey(card.getExpansionSetCode())) {
final CardCriteria cardCriteria = new CardCriteria();
cardCriteria.setCodes(sets);
cardCriteria.name(card.getName());
java.util.List<CardInfo> cardPool = CardRepository.instance.findCards(cardCriteria);
if (!cardPool.isEmpty()) {
Card acard = cardPool.get(RandomUtil.nextInt(cardPool.size())).getMockCard();
if (acard.getName().equals(card.getName())) {
CardView pimpedCard = new CardView(acard);
addCardView(pimpedCard, false);
eventSource.addSpecificCard(pimpedCard, "add-specific-card");
pimpedCards.put(pimpedCard, 1);
didModify = true;
}
}
}
}
}
if (didModify) {
for (CardView c : pimpedCards.keySet()) {
sortIntoGrid(c);
}
layoutGrid();
cardScroll.revalidate();
repaint();
JOptionPane.showMessageDialog(null, "Added " + pimpedCards.size() + " cards. You can select them and the originals by choosing 'Multiples'");
}
}
}
// Update the contents of the card grid
public void setCards(CardsView cardsView, DeckCardLayout layout, BigCard bigCard) {
if (bigCard != null) {
lastBigCard = bigCard;
}
// Remove all of the cards not in the cardsView
boolean didModify = false; // Until contested
for (int i = 0; i < cardGrid.size(); ++i) {
ArrayList<ArrayList<CardView>> gridRow = cardGrid.get(i);
for (int j = 0; j < gridRow.size(); ++j) {
ArrayList<CardView> stack = gridRow.get(j);
for (int k = 0; k < stack.size(); ++k) {
CardView card = stack.get(k);
if (!cardsView.containsKey(card.getId())) {
// Remove it
removeCardView(card);
stack.remove(k--);
// Mark
didModify = true;
}
}
}
}
// Trim the grid
if (didModify) {
trimGrid();
}
if (layout == null) {
// No layout -> add any new card views one at a time as par the current sort
for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) {
// Is a new card
addCardView(newCard, false);
// Put it into the appropirate place in the grid given the current sort
sortIntoGrid(newCard);
// Mark
didModify = true;
}
}
} else {
// Layout given -> Build card grid using layout, and set sort / separate
// Always modify when given a layout
didModify = true;
// Load in settings
loadSettings(Settings.parse(layout.getSettings()));
// Traverse the cards once and track them so we can pick ones to insert into the grid
Map<String, Map<String, ArrayList<CardView>>> trackedCards = new HashMap<>();
for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) {
// Add the new card
addCardView(newCard, false);
// Add the new card to tracking
Map<String, ArrayList<CardView>> forSetCode;
if (trackedCards.containsKey(newCard.getExpansionSetCode())) {
forSetCode = trackedCards.get(newCard.getExpansionSetCode());
} else {
forSetCode = new HashMap<>();
trackedCards.put(newCard.getExpansionSetCode(), forSetCode);
}
ArrayList<CardView> list;
if (forSetCode.containsKey(newCard.getCardNumber())) {
list = forSetCode.get(newCard.getCardNumber());
} else {
list = new ArrayList<>();
forSetCode.put(newCard.getCardNumber(), list);
}
list.add(newCard);
}
}
// Now go through the layout and use it to build the cardGrid
cardGrid = new ArrayList<>();
maxStackSize = new ArrayList<>();
for (java.util.List<java.util.List<DeckCardInfo>> row : layout.getCards()) {
ArrayList<ArrayList<CardView>> gridRow = new ArrayList<>();
int thisMaxStackSize = 0;
cardGrid.add(gridRow);
for (java.util.List<DeckCardInfo> stack : row) {
ArrayList<CardView> gridStack = new ArrayList<>();
gridRow.add(gridStack);
for (DeckCardInfo info : stack) {
if (trackedCards.containsKey(info.getSetCode()) && trackedCards.get(info.getSetCode()).containsKey(info.getCardNum())) {
ArrayList<CardView> candidates
= trackedCards.get(info.getSetCode()).get(info.getCardNum());
if (!candidates.isEmpty()) {
gridStack.add(candidates.remove(0));
thisMaxStackSize = Math.max(thisMaxStackSize, gridStack.size());
}
}
}
}
maxStackSize.add(thisMaxStackSize);
}
// Check that there aren't any "orphans" not referenced in the layout. There should
// never be any under normal operation, but as a failsafe in case the user screwed with
// the file in an invalid way, sort them into the grid so that they aren't just left hanging.
for (Map<String, ArrayList<CardView>> tracked : trackedCards.values()) {
for (ArrayList<CardView> orphans : tracked.values()) {
for (CardView orphan : orphans) {
LOGGER.info("Orphan when setting with layout: ");
sortIntoGrid(orphan);
}
}
}
}
// Modifications?
if (didModify) {
// Update layout
layoutGrid();
// Update draw
cardScroll.revalidate();
repaint();
}
}
private int getCount(CardType cardType) {
if (null != cardType) {
switch (cardType) {
case CREATURE:
return creatureCounter.get();
case LAND:
return landCounter.get();
case ARTIFACT:
return artifactCounter.get();
case ENCHANTMENT:
return enchantmentCounter.get();
case INSTANT:
return instantCounter.get();
case PLANESWALKER:
return planeswalkerCounter.get();
case SORCERY:
return sorceryCounter.get();
case TRIBAL:
return tribalCounter.get();
default:
break;
}
}
return 0;
}
private void updateCounts() {
deckNameAndCountLabel.setText(role.getName() + " - " + allCards.size());
creatureCountLabel.setText(String.valueOf(creatureCounter.get()));
landCountLabel.setText(String.valueOf(landCounter.get()));
for (CardType cardType : selectByTypeButtons.keySet()) {
AbstractButton button = selectByTypeButtons.get(cardType);
String text = cardType.toString();
int numCards = getCount(cardType);
if (cardType == cardType.CONSPIRACY) {
continue;
}
if (numCards > 0) {
button.setForeground(Color.BLACK);
text = text + " - " + numCards;
} else {
button.setForeground(new Color(100, 100, 100));
}
button.setText(text);
}
}
private void showCardRightClickMenu(@SuppressWarnings("unused") final CardView card, MouseEvent e) {
JPopupMenu menu = new JPopupMenu();
JMenuItem hide = new JMenuItem("Hide");
hide.addActionListener(e2 -> hideSelection());
menu.add(hide);
JMenuItem invertSelection = new JMenuItem("Invert Selection");
invertSelection.addActionListener(e2 -> invertSelection());
menu.add(invertSelection);
// Show 'Duplicate Selection' for FREE_BUILDING
if (this.mode == Constants.DeckEditorMode.FREE_BUILDING) {
JMenuItem duplicateSelection = new JMenuItem("Duplicate Selection");
duplicateSelection.addActionListener(e2 -> duplicateSelection());
menu.add(duplicateSelection);
}
menu.show(e.getComponent(), e.getX(), e.getY());
}
public void addCardView(final CardView card, boolean duplicated) {
allCards.add(card);
// Update counts
for (CardTypeCounter counter : allCounters) {
counter.add(card);
}
updateCounts();
// Create the card view
final MageCard cardPanel = Plugins.instance.getMageCard(card, lastBigCard, new Dimension(getCardWidth(), getCardHeight()), null, true, true);
cardPanel.update(card);
cardPanel.setTextOffset(0);
// Remove mouse wheel listeners so that scrolling works
// Scrolling works on all areas without cards or by using the scroll bar, that's enough
// for (MouseWheelListener l : cardPanel.getMouseWheelListeners()) {
// cardPanel.removeMouseWheelListener(l);
// }
// Add a click listener for selection / drag start
cardPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
// Select if not selected
if (!card.isSelected()) {
selectCard(card);
}
// Show menu
showCardRightClickMenu(card, e);
} else if (SwingUtilities.isLeftMouseButton(e)) {
if (e.getClickCount() == 1) {
cardClicked(card, e);
} else if (e.isAltDown()) {
eventSource.altDoubleClick(card, "alt-double-click");
} else {
eventSource.doubleClick(card, "double-click");
}
}
}
});
// Add a motion listener to process drags
cardPanel.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (!dragger.isDragging()) {
// If the card isn't already selected, make sure it is
if (!card.isSelected()) {
cardClicked(card, e);
}
dragger.beginDrag(cardPanel, e);
}
}
});
// And add it
cardContent.add(cardPanel);
cardViews.put(card.getId(), cardPanel);
if (duplicated) {
sortIntoGrid(card);
eventSource.addSpecificCard(card, "add-specific-card");
// Update layout
layoutGrid();
// Update draw
cardScroll.revalidate();
repaint();
}
}
private final ArrayList<DragCardGridListener> listeners = new ArrayList<>();
public void addDragCardGridListener(DragCardGridListener l) {
listeners.add(l);
}
private void notifyCardsSelected() {
for (DragCardGridListener listener : listeners) {
listener.cardsSelected();
}
}
private void selectCard(CardView targetCard) {
// Set the selected card to the target card
for (CardView card : allCards) {
if (card == targetCard) {
if (!card.isSelected()) {
card.setSelected(true);
cardViews.get(card.getId()).update(card);
}
} else if (card.isSelected()) {
card.setSelected(false);
cardViews.get(card.getId()).update(card);
}
}
}
private void toggleSelected(CardView targetCard) {
targetCard.setSelected(!targetCard.isSelected());
cardViews.get(targetCard.getId()).update(targetCard);
}
private void cardClicked(CardView targetCard, MouseEvent e) {
if (e.isShiftDown()) {
toggleSelected(targetCard);
} else {
selectCard(targetCard);
}
notifyCardsSelected();
}
private void removeCardView(CardView card) {
allCards.remove(card);
// Remove fromcounts
for (CardTypeCounter counter : allCounters) {
counter.remove(card);
}
updateCounts();
cardContent.remove(cardViews.get(card.getId()));
cardViews.remove(card.getId());
}
/**
* Add a card to the cardGrid, in the position that the current sort
* dictates
*
* @param newCard Card to add to the cardGrid array.
*/
private void sortIntoGrid(CardView newCard) {
// Ensure row 1 exists
if (cardGrid.isEmpty()) {
cardGrid.add(0, new ArrayList<>());
maxStackSize.add(0, 0);
}
// What row to add it to?
ArrayList<ArrayList<CardView>> targetRow;
if (separateCreatures && !newCard.isCreature()) {
// Ensure row 2 exists
if (cardGrid.size() < 2) {
cardGrid.add(1, new ArrayList<>());
maxStackSize.add(1, 0);
// Populate with stacks matching the first row
for (int i = 0; i < cardGrid.get(0).size(); ++i) {
cardGrid.get(1).add(new ArrayList<>());
}
}
targetRow = cardGrid.get(1);
} else {
targetRow = cardGrid.get(0);
}
// Find the right column to insert into
boolean didInsert = false;
for (int currentColumn = 0; currentColumn < cardGrid.get(0).size(); ++currentColumn) {
// Find an item from this column
CardView cardInColumn = null;
for (ArrayList<ArrayList<CardView>> gridRow : cardGrid) {
for (CardView card : gridRow.get(currentColumn)) {
cardInColumn = card;
break;
}
}
// No card in this column?
if (cardInColumn == null) {
// Error, should not have an empty column
LOGGER.error("Empty column! " + currentColumn);
} else {
int res = cardSort.getComparator().compare(newCard, cardInColumn);
if (res <= 0) {
// Insert into this col, but if less, then we need to create a new col here first
if (res < 0) {
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
cardGrid.get(rowIndex).add(currentColumn, new ArrayList<>());
}
}
targetRow.get(currentColumn).add(newCard);
didInsert = true;
break;
} else {
// Nothing to do, go to next iteration
}
}
}
// If nothing else, insert in a new column after everything else
if (!didInsert) {
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
cardGrid.get(rowIndex).add(new ArrayList<>());
}
targetRow.get(targetRow.size() - 1).add(newCard);
}
}
/**
* Delete any empty columns / rows from the grid, and eleminate any empty
* space in stacks
*/
private void trimGrid() {
// Compact stacks and rows
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
ArrayList<ArrayList<CardView>> gridRow = cardGrid.get(rowIndex);
int rowMaxStackSize = 0;
for (ArrayList<CardView> stack : gridRow) {
// Clear out nulls in the stack
for (int i = 0; i < stack.size(); ++i) {
if (stack.get(i) == null) {
stack.remove(i--);
}
}
// Is the stack still non-empty?
rowMaxStackSize = Math.max(rowMaxStackSize, stack.size());
}
// Is the row empty? If so remove it
if (rowMaxStackSize == 0) {
cardGrid.remove(rowIndex);
maxStackSize.remove(rowIndex);
--rowIndex;
} else {
maxStackSize.set(rowIndex, rowMaxStackSize);
}
}
// Remove empty columns
if (!cardGrid.isEmpty()) {
for (int colIndex = 0; colIndex < cardGrid.get(0).size(); ++colIndex) {
boolean hasContent = false; // Until contested
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
if (!cardGrid.get(rowIndex).get(colIndex).isEmpty()) {
hasContent = true;
break;
}
}
if (!hasContent) {
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
cardGrid.get(rowIndex).remove(colIndex);
}
--colIndex;
}
}
}
// Clean up extra column header count labels
while (stackCountLabels.size() > cardGrid.size()) {
ArrayList<JLabel> labels = stackCountLabels.remove(cardGrid.size());
for (JLabel label : labels) {
cardContent.remove(label);
}
}
int colCount = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
for (ArrayList<JLabel> labels : stackCountLabels) {
while (labels.size() > colCount) {
cardContent.remove(labels.remove(colCount));
}
}
}
private int getCardWidth() {
return (int) (GUISizeHelper.editorCardDimension.width * cardSizeMod);
}
private int getCardHeight() {
return (int) (1.4 * getCardWidth());
}
/**
* Position all of the card views correctly
*/
private void layoutGrid() {
// Basic dimensions
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
// Layout one at a time
int layerIndex = 0;
int currentY = COUNT_LABEL_HEIGHT;
int maxWidth = 0;
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
int rowMaxStackSize = 0;
ArrayList<ArrayList<CardView>> gridRow = cardGrid.get(rowIndex);
for (int colIndex = 0; colIndex < gridRow.size(); ++colIndex) {
ArrayList<CardView> stack = gridRow.get(colIndex);
// Stack count label
if (stackCountLabels.size() <= rowIndex) {
stackCountLabels.add(new ArrayList<>());
}
if (stackCountLabels.get(rowIndex).size() <= colIndex) {
JLabel countLabel = new JLabel("", SwingConstants.CENTER);
countLabel.setForeground(Color.WHITE);
cardContent.add(countLabel, new Integer(0));
stackCountLabels.get(rowIndex).add(countLabel);
}
JLabel countLabel = stackCountLabels.get(rowIndex).get(colIndex);
if (stack.isEmpty()) {
countLabel.setVisible(false);
} else {
countLabel.setText(String.valueOf(stack.size()));
countLabel.setLocation(GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex, currentY - COUNT_LABEL_HEIGHT);
countLabel.setSize(cardWidth, COUNT_LABEL_HEIGHT);
countLabel.setVisible(true);
}
// Max stack size
rowMaxStackSize = Math.max(rowMaxStackSize, stack.size());
// Layout cards in stack
for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i);
MageCard view = cardViews.get(card.getId());
int x = GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex;
int y = currentY + i * cardTopHeight;
view.setCardBounds(x, y, cardWidth, cardHeight);
cardContent.setLayer(view, layerIndex++);
}
}
// Update the max stack size for this row and the max width
maxWidth = Math.max(maxWidth, GRID_PADDING + (GRID_PADDING + cardWidth) * gridRow.size());
maxStackSize.set(rowIndex, rowMaxStackSize);
currentY += (cardTopHeight * (rowMaxStackSize - 1) + cardHeight) + COUNT_LABEL_HEIGHT;
}
// Resize card container
cardContent.setPreferredSize(new Dimension(maxWidth, currentY - COUNT_LABEL_HEIGHT + GRID_PADDING));
//cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + GRID_PADDING);
}
private static void makeButtonPopup(final AbstractButton button, final JPopupMenu popup) {
button.addActionListener(e -> popup.show(button, 0, button.getHeight()));
}
}
/**
* Note: This class can't just be a JPanel, because a JPanel doesn't draw when
* it has Opaque = false, but this class needs to go into a JLayeredPane while
* being translucent, so it NEEDS Opaque = false in order to behave correctly.
* Thus this simple class is needed to implement a translucent box in a
* JLayeredPane.
*/
class SelectionBox extends JComponent {
public SelectionBox() {
setOpaque(false);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g = g.create();
g.setColor(new Color(100, 100, 200, 128));
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(0, 0, 255));
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g.dispose();
}
}