From f6d50ce04f1471479df1047677d6424a119fa53b Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Tue, 4 Oct 2016 00:04:13 -0600 Subject: [PATCH] Various new Drag & Drop deck editor improvements * Shift-Click / Shift-Drag now work as expected as far as multi-selection * Deck editor saves split pane split positions * Card layout and sort settings are now saved along side the a deck when saving to the .dck format, so that you have back the exact same deck layout when you re-load the deck. * Fixed the symbol image downloader to work around some of the large-size symbol images being missing on gatherer. Falls back to the medium sized images currently for those symbols. --- .../java/mage/client/cards/DragCardGrid.java | 439 +++++++++--------- .../java/mage/client/deckeditor/DeckArea.java | 70 ++- .../client/deckeditor/DeckEditorPanel.java | 62 ++- .../mage/client/dialog/PreferencesDialog.java | 4 + .../org/mage/card/arcane/ManaSymbols.java | 8 +- .../card/dl/sources/GathererSymbols.java | 24 +- Mage/src/main/java/mage/cards/Sets.java | 32 ++ Mage/src/main/java/mage/cards/decks/Deck.java | 12 + .../java/mage/cards/decks/DeckCardLayout.java | 24 + .../java/mage/cards/decks/DeckCardLists.java | 20 + .../cards/decks/importer/DckDeckImporter.java | 59 +++ 11 files changed, 516 insertions(+), 238 deletions(-) create mode 100644 Mage/src/main/java/mage/cards/decks/DeckCardLayout.java diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index fa206cb0f63..a04d2e963cd 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -2,8 +2,12 @@ package mage.client.cards; import mage.cards.MageCard; import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; import mage.cards.decks.importer.DeckImporterUtil; +import mage.cards.repository.CardInfo; import mage.client.MageFrame; +import mage.client.deckeditor.DeckArea; import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.*; @@ -26,7 +30,12 @@ import java.awt.*; import java.awt.event.*; import java.io.File; import java.io.IOException; +import java.io.Serializable; import java.util.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Created by StravantUser on 2016-09-20. @@ -379,6 +388,23 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg cardContent.repaint(); } + public DeckCardLayout getCardLayout() { + // 2D Array to put entries into + List>> info = new ArrayList<>(); + for (ArrayList> gridRow : cardGrid) { + List> row = new ArrayList<>(); + info.add(row); + for (ArrayList 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 enum Sort { NONE("No Sort", new Comparator() { @Override @@ -476,6 +502,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg JPopupMenu sortPopup; JCheckBox separateCreaturesCb; + Map sortButtons = new HashMap<>(); + JLabel deckNameAndCountLabel; JLabel landCountLabel; JLabel creatureCountLabel; @@ -489,6 +517,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // Card area selection panel SelectionBox selectionPanel; + Set selectionDragStartCards; int selectionDragStartX; int selectionDragStartY; @@ -526,6 +555,55 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private String name; } + public static class Settings { + public Sort sort; + public boolean separateCreatures; + + private static Pattern parser = Pattern.compile("\\(([^,]*),([^)]*)\\)"); + + public static Settings parse(String str) { + Matcher m = parser.matcher(str); + if (m.find()) { + Settings s = new Settings(); + s.sort = Sort.valueOf(m.group(1)); + s.separateCreatures = Boolean.valueOf(m.group(2)); + return s; + } else { + return null; + } + } + + @Override + public String toString() { + return "(" + sort.toString() + "," + Boolean.toString(separateCreatures) + ")"; + } + } + + public Settings saveSettings() { + Settings s = new Settings(); + s.sort = cardSort; + s.separateCreatures = separateCreatures; + return s; + } + + public void loadSettings(Settings s) { + if (s != null) { + setSort(s.sort); + setSeparateCreatures(s.separateCreatures); + resort(); + } + } + + public void setSeparateCreatures(boolean state) { + separateCreatures = state; + separateCreaturesCb.setSelected(state); + } + + public void setSort(Sort s) { + cardSort = s; + sortButtons.get(s).setSelected(true); + } + // Constructor public DragCardGrid() { // Make sure that the card grid is populated with at least one (empty) stack to begin with @@ -540,22 +618,6 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg filterButton = new JButton("Filter"); visibilityButton = new JButton("Visibility"); - addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - deselectAll(); - } - }); - - // Tmp load button - JButton loadButton = new JButton("Load"); - loadButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - loadDeck(); - } - }); - // Name and count label deckNameAndCountLabel = new JLabel(); @@ -576,25 +638,21 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg toolbarInner.add(sortButton); toolbarInner.add(filterButton); toolbarInner.add(visibilityButton); - toolbarInner.add(loadButton); toolbar.add(toolbarInner, BorderLayout.WEST); JPanel sliderPanel = new JPanel(new GridBagLayout()); sliderPanel.setOpaque(false); final JSlider sizeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 50); sizeSlider.setOpaque(false); sizeSlider.setPreferredSize(new Dimension(100, (int)sizeSlider.getPreferredSize().getHeight())); - sizeSlider.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (!sizeSlider.getValueIsAdjusting()) { - // Fraction in [-1, 1] - float sliderFrac = ((float) (sizeSlider.getValue() - 50)) / 50; - // Convert to frac in [0.5, 2.0] exponentially - cardSizeMod = (float) Math.pow(2, sliderFrac); - // Update grid - layoutGrid(); - cardContent.repaint(); - } + sizeSlider.addChangeListener(e -> { + if (!sizeSlider.getValueIsAdjusting()) { + // Fraction in [-1, 1] + float sliderFrac = ((float) (sizeSlider.getValue() - 50)) / 50; + // Convert to frac in [0.5, 2.0] exponentially + cardSizeMod = (float) Math.pow(2, sliderFrac); + // Update grid + layoutGrid(); + cardContent.repaint(); } }); sliderPanel.add(new JLabel("Card Size:")); @@ -610,9 +668,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private boolean isDragging = false; @Override public void mousePressed(MouseEvent e) { - isDragging = true; - beginSelectionDrag(e.getX(), e.getY()); - updateSelectionDrag(e.getX(), e.getY()); + if (SwingUtilities.isLeftMouseButton(e)) { + isDragging = true; + beginSelectionDrag(e.getX(), e.getY(), e.isShiftDown()); + updateSelectionDrag(e.getX(), e.getY()); + } } @Override public void mouseReleased(MouseEvent e) { @@ -673,15 +733,13 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg if (s == cardSort) { button.setSelected(true); } + sortButtons.put(s, button); sortMode.add(button); sortModeGroup.add(button); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - cardSort = s; - PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SORT, s.toString()); - resort(); - } + button.addActionListener(e -> { + cardSort = s; + PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SORT, s.toString()); + resort(); }); } @@ -695,13 +753,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg separateCreaturesCb = new JCheckBox(); separateCreaturesCb.setText("Creatures in separate row"); separateCreaturesCb.setSelected(separateCreatures); - separateCreaturesCb.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - separateCreatures = separateCreaturesCb.isSelected(); - PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, Boolean.toString(separateCreatures)); - resort(); - } + separateCreaturesCb.addItemListener(e -> { + setSeparateCreatures(separateCreaturesCb.isSelected()); + PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, Boolean.toString(separateCreatures)); + resort(); }); sortOptions.add(separateCreaturesCb); @@ -714,20 +769,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg { final JPopupMenu visPopup = new JPopupMenu(); JMenuItem hideSelected = new JMenuItem("Hide selected"); - hideSelected.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - hideSelection(); - } - }); + hideSelected.addActionListener(e -> hideSelection()); visPopup.add(hideSelected); JMenuItem showAll = new JMenuItem("Show all"); - showAll.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showAll(); - } - }); + showAll.addActionListener(e -> showAll()); visPopup.add(showAll); visibilityButton.addMouseListener(new MouseAdapter() { @Override @@ -743,7 +788,6 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg makeButtonPopup(filterButton, filterPopup); filterButton.setVisible(false); - loadButton.setVisible(false); // Right click in card area initCardAreaPopup(); @@ -756,44 +800,29 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg final JPopupMenu menu = new JPopupMenu(); final JMenuItem hideSelected = new JMenuItem("Hide selected"); - hideSelected.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - hideSelection(); - } - }); + hideSelected.addActionListener(e -> hideSelection()); menu.add(hideSelected); JMenuItem showAll = new JMenuItem("Show all"); - showAll.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showAll(); - } - }); + showAll.addActionListener(e -> showAll()); menu.add(showAll); JMenu sortMenu = new JMenu("Sort by..."); + final Map sortMenuItems = new LinkedHashMap<>(); for (final Sort sort : Sort.values()) { - JMenuItem subSort = new JMenuItem(sort.getText()); - subSort.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - cardSort = sort; - resort(); - } + 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(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - separateCreatures = !separateCreatures; - separateCreaturesCb.setSelected(separateCreatures); - resort(); - } + separateButton.addActionListener(e -> { + setSeparateCreatures(!separateCreatures); + resort(); }); sortMenu.add(separateButton); menu.add(sortMenu); @@ -803,6 +832,9 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { + for (Sort s : sortMenuItems.keySet()) { + sortMenuItems.get(s).setSelected(cardSort == s); + } hideSelected.setEnabled(dragCardList().size() > 0); separateButton.setSelected(separateCreatures); menu.show(e.getComponent(), e.getX(), e.getY()); @@ -843,7 +875,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg /** * Selection drag handling */ - private void beginSelectionDrag(int x, int y) { + private void beginSelectionDrag(int x, int y, boolean shiftHeld) { // Show the selection panel selectionPanel.setVisible(true); selectionPanel.setLocation(x, y); @@ -853,6 +885,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg selectionDragStartX = x; selectionDragStartY = y; + // Store the starting cards to include in the selection + selectionDragStartCards = new HashSet<>(); + if (shiftHeld) { + selectionDragStartCards.addAll(dragCardList()); + } + // Notify selection notifyCardsSelected(); } @@ -904,7 +942,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg boolean inBoundsX = (col >= col1 && col <= col2); boolean inBoundsY = (i >= stackStartIndex && i <= stackEndIndex); boolean lastCard = (i == stack.size()-1); - if (inBoundsX && (inBoundsY || (lastCard && (y2 >= stackBottomBegin && y1 <= stackBottomEnd)))) { + boolean inSeletionDrag = inBoundsX && (inBoundsY || (lastCard && (y2 >= stackBottomBegin && y1 <= stackBottomEnd))); + if (inSeletionDrag || selectionDragStartCards.contains(card)) { if (!card.isSelected()) { card.setSelected(true); view.update(card); @@ -921,42 +960,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } } - private void endSelectionDrag(int x, int y) { + private void endSelectionDrag(@SuppressWarnings("unused") int x, @SuppressWarnings("unused") int y) { // Hide the selection panel selectionPanel.setVisible(false); } - - private void loadDeck() { - JFileChooser fcSelectDeck = new JFileChooser(); - String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", ""); - if (!lastFolder.isEmpty()) { - fcSelectDeck.setCurrentDirectory(new File(lastFolder)); - } - int ret = fcSelectDeck.showOpenDialog(DragCardGrid.this); - if (ret == JFileChooser.APPROVE_OPTION) { - File file = fcSelectDeck.getSelectedFile(); - try { - setCursor(new Cursor(Cursor.WAIT_CURSOR)); - Deck deck = Deck.load(DeckImporterUtil.importDeck(file.getPath()), true, true); - setCards(new CardsView(deck.getCards()), null); - } catch (GameException ex) { - JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); - } - try { - if (file != null) { - MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); - } - } catch (IOException ex) { - } - } - fcSelectDeck.setSelectedFile(null); - } - // Resort the existing cards based on the current sort public void resort() { // First null out the grid and trim it down @@ -984,7 +992,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } // Update the contents of the card grid - public void setCards(CardsView cardsView, BigCard bigCard) { + public void setCards(CardsView cardsView, DeckCardLayout layout, BigCard bigCard) { if (bigCard != null) { lastBigCard = bigCard; } @@ -1014,20 +1022,89 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg trimGrid(); } - // Add any new card views - for (CardView newCard: cardsView.values()) { - if (!cardViews.containsKey(newCard.getId())) { - // Is a new card - addCardView(newCard); + 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); - try { // Put it into the appropirate place in the grid given the current sort sortIntoGrid(newCard); - } catch (Exception e) { - e.printStackTrace(); + + // 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>> trackedCards = new HashMap<>(); + for (CardView newCard: cardsView.values()) { + if (!cardViews.containsKey(newCard.getId())) { + // Add the new card + addCardView(newCard); + + // Add the new card to tracking + Map> forSetCode; + if (trackedCards.containsKey(newCard.getExpansionSetCode())) { + forSetCode = trackedCards.get(newCard.getExpansionSetCode()); + } else { + forSetCode = new HashMap<>(); + trackedCards.put(newCard.getExpansionSetCode(), forSetCode); + } + ArrayList 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 (List> row : layout.getCards()) { + ArrayList> gridRow = new ArrayList<>(); + int thisMaxStackSize = 0; + cardGrid.add(gridRow); + for (List stack : row) { + ArrayList gridStack = new ArrayList<>(); + gridRow.add(gridStack); + for (DeckCardInfo info : stack) { + if (trackedCards.containsKey(info.getSetCode()) && trackedCards.get(info.getSetCode()).containsKey(info.getCardNum())) { + ArrayList candidates = + trackedCards.get(info.getSetCode()).get(info.getCardNum()); + if (candidates.size() > 0) { + 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> tracked : trackedCards.values()) { + for (ArrayList orphans : tracked.values()) { + for (CardView orphan : orphans) { + LOGGER.info("Orphan when setting with layout: "); + sortIntoGrid(orphan); + } } - // Mark - didModify = true; } } @@ -1048,18 +1125,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg landCountLabel.setText("" + landCounter.get()); } - private void showCardRightClickMenu(final CardView card, MouseEvent e) { + private void showCardRightClickMenu(@SuppressWarnings("unused") final CardView card, MouseEvent e) { JPopupMenu menu = new JPopupMenu(); JMenuItem hide = new JMenuItem("Hide"); - hide.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - //if (card.isSelected() && dragCardList().size() > 1) { - // Hide all selected - hideSelection(); - //} - } - }); + hide.addActionListener(e2 -> hideSelection()); menu.add(hide); menu.show(e.getComponent(), e.getX(), e.getY()); } @@ -1156,8 +1225,17 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } } + private void toggleSelected(CardView targetCard) { + targetCard.setSelected(!targetCard.isSelected()); + cardViews.get(targetCard.getId()).update(targetCard); + } + private void cardClicked(CardView targetCard, MouseEvent e) { - selectCard(targetCard); + if (e.isShiftDown()) { + toggleSelected(targetCard); + } else { + selectCard(targetCard); + } notifyCardsSelected(); } @@ -1176,12 +1254,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg /** * Add a card to the cardGrid, in the position that the current sort dictates - * @param newCard + * @param newCard Card to add to the cardGrid array. */ private void sortIntoGrid(CardView newCard) { // Ensure row 1 exists if (cardGrid.size() == 0) { - cardGrid.add(0, new ArrayList>()); + cardGrid.add(0, new ArrayList<>()); maxStackSize.add(0, 0); } // What row to add it to? @@ -1189,11 +1267,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg if (separateCreatures && !newCard.getCardTypes().contains(CardType.CREATURE)) { // Ensure row 2 exists if (cardGrid.size() < 2) { - cardGrid.add(1, new ArrayList>()); + 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()); + cardGrid.get(1).add(new ArrayList<>()); } } targetRow = cardGrid.get(1); @@ -1223,7 +1301,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // 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()); + cardGrid.get(rowIndex).add(currentColumn, new ArrayList<>()); } } targetRow.get(currentColumn).add(newCard); @@ -1238,7 +1316,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // 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()); + cardGrid.get(rowIndex).add(new ArrayList<>()); } targetRow.get(targetRow.size()-1).add(newCard); } @@ -1335,7 +1413,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // Stack count label if (stackCountLabels.size() <= rowIndex) { - stackCountLabels.add(new ArrayList()); + stackCountLabels.add(new ArrayList<>()); } if (stackCountLabels.get(rowIndex).size() <= colIndex) { JLabel countLabel = new JLabel("", SwingConstants.CENTER); @@ -1379,70 +1457,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } private static void makeButtonPopup(final AbstractButton button, final JPopupMenu popup) { - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - popup.show(button, 0, button.getHeight()); - } - }); - } - - static class DeckFilter extends FileFilter { - @Override - public boolean accept(File f) { - if (f.isDirectory()) { - return true; - } - - String ext = null; - String s = f.getName(); - int i = s.lastIndexOf('.'); - - if (i > 0 && i < s.length() - 1) { - ext = s.substring(i + 1).toLowerCase(); - } - return (ext == null) ? false : ext.equals("dck"); - } - - @Override - public String getDescription() { - return "Deck Files"; - } - } - - public static void main(String[] args) { - JFrame frame = new JFrame(); - /* - GUISizeHelper.calculateGUISizes(); - Plugins.getInstance().loadPlugins(); - frame.setTitle("Test"); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.setBackground(Color.BLUE); - DragCardGrid grid = new DragCardGrid(); - grid.setPreferredSize(new Dimension(800, 600)); - */ - try { - UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); - } catch (UnsupportedLookAndFeelException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {} - frame.setVisible(true); - JFileChooser choose = new JFileChooser(); - choose.setAcceptAllFileFilterUsed(false); - choose.addChoosableFileFilter(new DeckFilter()); - choose.showOpenDialog(frame); - LOGGER.info("File: " + choose.getSelectedFile()); - String st = ""; - try { - st = choose.getSelectedFile().getCanonicalPath(); - } catch (Exception e) { - e.printStackTrace(); - } - LOGGER.info("Selected file: " + st); - choose.setSelectedFile(new File(st)); - choose.showOpenDialog(frame); - LOGGER.info("File: " + choose.getSelectedFile()); - //frame.add(grid, BorderLayout.CENTER); - //frame.pack(); - frame.setVisible(false); + button.addActionListener(e -> popup.show(button, 0, button.getHeight())); } } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java index 68dd871c894..4040e1658e9 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java @@ -34,6 +34,9 @@ package mage.client.deckeditor; import mage.cards.Card; import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; +import mage.cards.decks.DeckCardLists; import mage.client.cards.BigCard; import mage.client.cards.CardEventSource; import mage.client.cards.DragCardGrid; @@ -43,9 +46,12 @@ import mage.client.util.GUISizeHelper; import mage.client.util.Listener; import mage.view.CardView; import mage.view.CardsView; +import org.apache.log4j.Logger; import javax.swing.*; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @@ -59,6 +65,40 @@ public class DeckArea extends javax.swing.JPanel { private Deck lastDeck = new Deck(); private BigCard lastBigCard = null; + public DeckCardLayout getCardLayout() { + return deckList.getCardLayout(); + } + + public DeckCardLayout getSideboardLayout() { + return sideboardList.getCardLayout(); + } + + public static class Settings { + public DragCardGrid.Settings maindeckSettings; + public DragCardGrid.Settings sideboardSetings; + public int dividerLocation; + + private static Pattern parser = Pattern.compile("([^|]*)\\|([^|]*)\\|([^|]*)"); + + public static Settings parse(String s) { + Matcher m = parser.matcher(s); + if (m.find()) { + Settings settings = new Settings(); + settings.maindeckSettings = DragCardGrid.Settings.parse(m.group(1)); + settings.sideboardSetings = DragCardGrid.Settings.parse(m.group(2)); + settings.dividerLocation = Integer.parseInt(m.group(3)); + return settings; + } else { + return null; + } + } + + @Override + public String toString() { + return maindeckSettings.toString() + "|" + sideboardSetings.toString() + "|" + dividerLocation; + } + } + /** * Creates new form DeckArea */ @@ -117,6 +157,22 @@ public class DeckArea extends javax.swing.JPanel { }); } + public Settings saveSettings() { + Settings settings = new Settings(); + settings.maindeckSettings = deckList.saveSettings(); + settings.sideboardSetings = sideboardList.saveSettings(); + settings.dividerLocation = deckAreaSplitPane.getDividerLocation(); + return settings; + } + + public void loadSettings(Settings s) { + if (s != null) { + deckList.loadSettings(s.maindeckSettings); + sideboardList.loadSettings(s.sideboardSetings); + deckAreaSplitPane.setDividerLocation(s.dividerLocation); + } + } + public void cleanUp() { deckList.cleanUp(); sideboardList.cleanUp(); @@ -161,11 +217,21 @@ public class DeckArea extends javax.swing.JPanel { } public void loadDeck(Deck deck, BigCard bigCard) { + loadDeck(deck, false, bigCard); + } + + public void loadDeck(Deck deck, boolean useLayout, BigCard bigCard) { lastDeck = deck; lastBigCard = bigCard; - deckList.setCards(new CardsView(filterHidden(lastDeck.getCards())), lastBigCard); + deckList.setCards( + new CardsView(filterHidden(lastDeck.getCards())), + useLayout ? deck.getCardsLayout() : null, + lastBigCard); if (sideboardList.isVisible()) { - sideboardList.setCards(new CardsView(filterHidden(lastDeck.getSideboard())), lastBigCard); + sideboardList.setCards( + new CardsView(filterHidden(lastDeck.getSideboard())), + useLayout ? deck.getSideboardLayout() : null, + lastBigCard); } } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index a6ab56a1c66..a040df0464c 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -28,10 +28,7 @@ package mage.client.deckeditor; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; +import java.awt.event.*; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -50,6 +47,7 @@ import javax.swing.filechooser.FileFilter; import mage.cards.Card; import mage.cards.Sets; import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardLists; import mage.cards.decks.importer.DeckImporter; import mage.cards.decks.importer.DeckImporterUtil; import mage.cards.repository.CardInfo; @@ -62,6 +60,7 @@ import mage.client.constants.Constants.DeckEditorMode; import mage.client.deck.generator.DeckGenerator.DeckGeneratorException; import mage.client.deck.generator.DeckGenerator; import mage.client.dialog.AddLandDialog; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.Event; import mage.client.util.Listener; @@ -106,12 +105,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { deckArea.setOpaque(false); jPanel1.setOpaque(false); jSplitPane1.setOpaque(false); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - jSplitPane1.setDividerLocation(0.3); - } - }); + restoreDividerLocationsAndDeckAreaSettings(); countdown = new Timer(1000, new ActionListener() { @Override @@ -128,12 +122,22 @@ public class DeckEditorPanel extends javax.swing.JPanel { } } }); + + // Set up tracking to save the deck editor settings when the deck editor is hidden. + addHierarchyListener((HierarchyEvent e) -> { + if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { + if (!isShowing()) { + saveDividerLocationsAndDeckAreaSettings(); + } + } + }); } /** * Free resources so GC can remove unused objects from memory */ public void cleanUp() { + saveDividerLocationsAndDeckAreaSettings(); if (updateDeckTask != null) { updateDeckTask.cancel(true); } @@ -152,6 +156,24 @@ public class DeckEditorPanel extends javax.swing.JPanel { this.bigCard = null; } + private void saveDividerLocationsAndDeckAreaSettings() { + PreferencesDialog.saveValue(PreferencesDialog.KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION, Integer.toString(jSplitPane1.getDividerLocation())); + PreferencesDialog.saveValue(PreferencesDialog.KEY_EDITOR_DECKAREA_SETTINGS, this.deckArea.saveSettings().toString()); + } + + private void restoreDividerLocationsAndDeckAreaSettings() { + // Load horizontal split position setting + String dividerLocation = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION, ""); + if (!dividerLocation.isEmpty()) { + jSplitPane1.setDividerLocation(Integer.parseInt(dividerLocation)); + } + + // Load deck area settings + this.deckArea.loadSettings( + DeckArea.Settings.parse( + PreferencesDialog.getCachedValue(PreferencesDialog.KEY_EDITOR_DECKAREA_SETTINGS, ""))); + } + public void changeGUISize() { this.cardSelector.changeGUISize(); this.deckArea.changeGUISize(); @@ -556,10 +578,14 @@ public class DeckEditorPanel extends javax.swing.JPanel { } private void refreshDeck() { + refreshDeck(false); + } + + private void refreshDeck(boolean useLayout) { try { setCursor(new Cursor(Cursor.WAIT_CURSOR)); this.txtDeckName.setText(deck.getName()); - deckArea.loadDeck(deck, bigCard); + deckArea.loadDeck(deck, useLayout, bigCard); } finally { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } @@ -615,13 +641,6 @@ public class DeckEditorPanel extends javax.swing.JPanel { jSplitPane1.setTopComponent(cardSelector); jSplitPane1.setBottomComponent(deckArea); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - jSplitPane1.setDividerLocation(0.6); - } - }); - bigCard.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); cardInfoPane = Plugins.getInstance().getCardInfoPane(); @@ -878,7 +897,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } finally { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } - refreshDeck(); + refreshDeck(true); try { if (file != null) { MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); @@ -919,7 +938,10 @@ public class DeckEditorPanel extends javax.swing.JPanel { fileName += ".dck"; } setCursor(new Cursor(Cursor.WAIT_CURSOR)); - Sets.saveDeck(fileName, deck.getDeckCardLists()); + DeckCardLists cardLists = deck.getDeckCardLists(); + cardLists.setCardLayout(deckArea.getCardLayout()); + cardLists.setSideboardLayout(deckArea.getSideboardLayout()); + Sets.saveDeck(fileName, cardLists); } catch (FileNotFoundException ex) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage() + "\nTry ensuring that the selected directory is writable.", "Error saving deck", JOptionPane.ERROR_MESSAGE); } finally { diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 4f322571801..57ab43e6e32 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -185,6 +185,10 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_TABLES_DIVIDER_LOCATION_2 = "tablePanelDividerLocation2"; public static final String KEY_TABLES_DIVIDER_LOCATION_3 = "tablePanelDividerLocation3"; + // Positions of deck editor divider bars + public static final String KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION = "editorHorizontalDividerLocation"; + public static final String KEY_EDITOR_DECKAREA_SETTINGS = "editorDeckAreaSettings"; + // user list public static final String KEY_USERS_COLUMNS_WIDTH = "userPanelColumnWidth"; public static final String KEY_USERS_COLUMNS_ORDER = "userPanelColumnSort"; diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index d61c388b4fc..abcd2656d31 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -153,11 +153,13 @@ public class ManaSymbols { sizedSymbols.put(symbol, notResized); } else { Rectangle r = new Rectangle(size, size); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + //Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + BufferedImage image = ImageIO.read(file); + //BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + BufferedImage resized = ImageHelper.getResizedImage(image, r); sizedSymbols.put(symbol, resized); } - } catch (Exception e) { + } catch (IOException e) { LOGGER.error("Error for symbol:" + symbol); fileErrors = true; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index 7aaaa9d0ad0..485582d6a4f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -73,6 +73,28 @@ public class GathererSymbols implements Iterable { String symbol = sym.replaceAll("/", ""); File dst = new File(dir, symbol + ".gif"); + /** + * Handle a bug on Gatherer where a few symbols are missing at the large size. + * Fall back to using the medium symbol for those cases. + */ + int modSizeIndex = sizeIndex; + if (sizeIndex == 2) { + switch (sym) { + case "WP": + case "UP": + case "BP": + case "RP": + case "GP": + case "E": + case "C": + modSizeIndex = 1; + break; + + default: + // Nothing to do, symbol is available in the large size + } + } + switch (symbol) { case "T": symbol = "tap"; @@ -85,7 +107,7 @@ public class GathererSymbols implements Iterable { break; } - String url = format(urlFmt, sizes[sizeIndex], symbol); + String url = format(urlFmt, sizes[modSizeIndex], symbol); return new DownloadJob(sym, fromURL(url), toFile(dst)); } diff --git a/Mage/src/main/java/mage/cards/Sets.java b/Mage/src/main/java/mage/cards/Sets.java index 642bef29bb3..ee5f2ba64a3 100644 --- a/Mage/src/main/java/mage/cards/Sets.java +++ b/Mage/src/main/java/mage/cards/Sets.java @@ -33,6 +33,7 @@ import java.io.PrintWriter; import java.util.*; import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; @@ -166,16 +167,47 @@ public class Sets extends HashMap { } } + // Write out all of the cards for (Map.Entry entry: deckCards.entrySet()) { out.printf("%d [%s:%s] %s%n", entry.getValue().getQuantity(), entry.getValue().getSetCode(), entry.getValue().getCardNum(), entry.getValue().getCardName()); } for (Map.Entry entry: sideboard.entrySet()) { out.printf("SB: %d [%s:%s] %s%n", entry.getValue().getQuantity(), entry.getValue().getSetCode(), entry.getValue().getCardNum(), entry.getValue().getCardName()); } + + // Write out the layout + out.print("LAYOUT MAIN:"); + writeCardLayout(out, deck.getCardLayout()); + out.print("\n"); + out.print("LAYOUT SIDEBOARD:"); + writeCardLayout(out, deck.getSideboardLayout()); + out.print("\n"); } finally { out.close(); } } + private static void writeCardLayout(PrintWriter out, DeckCardLayout layout) { + List>> cardGrid = layout.getCards(); + int height = cardGrid.size(); + int width = (height > 0) ? cardGrid.get(0).size() : 0; + out.print("(" + height + "," + width + ")"); + out.print(layout.getSettings()); + out.print("|"); + for (List> row : cardGrid) { + for (List stack : row) { + out.print("("); + for (int i = 0; i < stack.size(); ++i) { + DeckCardInfo info = stack.get(i); + out.printf("[%s:%s]", info.getSetCode(), info.getCardNum()); + if (i != stack.size() - 1) { + out.print(","); + } + } + out.print(")"); + } + } + } + } diff --git a/Mage/src/main/java/mage/cards/decks/Deck.java b/Mage/src/main/java/mage/cards/decks/Deck.java index 4cd189a1ca2..3ee7dfd1943 100644 --- a/Mage/src/main/java/mage/cards/decks/Deck.java +++ b/Mage/src/main/java/mage/cards/decks/Deck.java @@ -44,6 +44,8 @@ public class Deck implements Serializable { private String name; private final Set cards = new LinkedHashSet<>(); private final Set sideboard = new LinkedHashSet<>(); + private DeckCardLayout cardsLayout; + private DeckCardLayout sideboardLayout; private long deckHashCode = 0; public static Deck load(DeckCardLists deckCardLists) throws GameException { @@ -57,6 +59,8 @@ public class Deck implements Serializable { public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException { Deck deck = new Deck(); deck.setName(deckCardLists.getName()); + deck.cardsLayout = deckCardLists.getCardLayout(); + deck.sideboardLayout = deckCardLists.getSideboardLayout(); List deckCardNames = new ArrayList<>(); for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) { Card card = createCard(deckCardInfo, mockCards); @@ -141,10 +145,18 @@ public class Deck implements Serializable { return cards; } + public DeckCardLayout getCardsLayout() { + return cardsLayout; + } + public Set getSideboard() { return sideboard; } + public DeckCardLayout getSideboardLayout() { + return sideboardLayout; + } + public long getDeckHashCode() { return deckHashCode; } diff --git a/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java b/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java new file mode 100644 index 00000000000..2cbab4ac286 --- /dev/null +++ b/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java @@ -0,0 +1,24 @@ +package mage.cards.decks; + +import java.util.List; + +/** + * Created by stravant@gmail.com on 2016-10-03. + */ +public class DeckCardLayout { + private List>> cards; + private String settings; + + public DeckCardLayout(List>> cards, String settings) { + this.cards = cards; + this.settings = settings; + } + + public List>> getCards() { + return cards; + } + + public String getSettings() { + return settings; + } +} diff --git a/Mage/src/main/java/mage/cards/decks/DeckCardLists.java b/Mage/src/main/java/mage/cards/decks/DeckCardLists.java index 7079d8996bc..2272aa3e114 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckCardLists.java +++ b/Mage/src/main/java/mage/cards/decks/DeckCardLists.java @@ -42,6 +42,26 @@ public class DeckCardLists implements Serializable { private List cards = new ArrayList<>(); private List sideboard = new ArrayList<>(); + // Layout (if supported) + private DeckCardLayout cardLayout = null; + private DeckCardLayout sideboardLayout = null; + + /** + * @return The layout of the cards + */ + public DeckCardLayout getCardLayout() { + return cardLayout; + } + public void setCardLayout(DeckCardLayout layout) { + this.cardLayout = layout; + } + public DeckCardLayout getSideboardLayout() { + return sideboardLayout; + } + public void setSideboardLayout(DeckCardLayout layout) { + this.sideboardLayout = layout; + } + /** * @return the cards */ diff --git a/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java index bb934de2879..38ebb24e46a 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java @@ -27,9 +27,12 @@ */ package mage.cards.decks.importer; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; @@ -42,6 +45,12 @@ public class DckDeckImporter extends DeckImporter { private static final Pattern pattern = Pattern.compile("(SB:)?\\s*(\\d*)\\s*\\[([^]:]+):([^]:]+)\\].*"); + private static final Pattern layoutPattern = Pattern.compile("LAYOUT (\\w+):\\((\\d+),(\\d+)\\)([^|]+)\\|(.*)$"); + + private static final Pattern layoutStackPattern = Pattern.compile("\\(([^)]*)\\)"); + + private static final Pattern layoutStackEntryPattern = Pattern.compile("\\[(\\w+):(\\w+)]"); + @Override protected void readLine(String line, DeckCardLists deckList) { @@ -79,6 +88,56 @@ public class DckDeckImporter extends DeckImporter { deckList.setName(line.substring(5, line.length())); } else if (line.startsWith("AUTHOR:")) { deckList.setAuthor(line.substring(7, line.length())); + } else if (line.startsWith("LAYOUT")) { + Matcher m2 = layoutPattern.matcher(line); + if (m2.find()) { + String target = m2.group(1); + int rows = Integer.parseInt(m2.group(2)); + int cols = Integer.parseInt(m2.group(3)); + String settings = m2.group(4); + String stackData = m2.group(5); + Matcher stackMatcher = layoutStackPattern.matcher(stackData); + // + List>> grid = new ArrayList<>(); + int totalCardCount = 0; + for (int row = 0; row < rows; ++row) { + List> rowData = new ArrayList<>(); + grid.add(rowData); + for (int col = 0; col < cols; ++col) { + List stack = new ArrayList<>(); + rowData.add(stack); + if (stackMatcher.find()) { + String thisStackData = stackMatcher.group(1); + Matcher stackEntries = layoutStackEntryPattern.matcher(thisStackData); + while (stackEntries.find()) { + ++totalCardCount; + stack.add(new DeckCardInfo("", stackEntries.group(2), stackEntries.group(1))); + } + } else { + sbMessage.append("Missing stack\n."); + } + } + } + // + DeckCardLayout layout = new DeckCardLayout(grid, settings); + int expectedCount = 0; + if (target.equals("MAIN")) { + deckList.setCardLayout(layout); + expectedCount = deckList.getCards().size(); + } else if (target.equals("SIDEBOARD")) { + deckList.setSideboardLayout(layout); + expectedCount = deckList.getSideboard().size(); + } else { + sbMessage.append("Bad target `" + target + "` for layout.\n"); + } + // + if (totalCardCount != expectedCount) { + sbMessage.append("Layout mismatch: Expected " + expectedCount + " cards, but got " + totalCardCount + " in layout `" + target + "`\n."); + } + + } else { + sbMessage.append("Malformed layout line"); + } } } }