From b823f4dcc779c2760e8f246c97c60d7ae0cd48b6 Mon Sep 17 00:00:00 2001 From: Failure Date: Wed, 13 Aug 2025 00:23:35 -0700 Subject: [PATCH] wip: tagger --- .../mage/client/deckeditor/CardSelector.java | 31 +- .../client/deckeditor/table/TaggerModel.java | 344 ++++++++++++++++++ .../java/mage/client/table/TablesPanel.java | 2 +- .../card/dl/sources/ScryfallApiCard.java | 2 +- .../card/dl/sources/ScryfallImageSource.java | 4 + .../java/mage/cards/repository/CardInfo.java | 2 + .../mage/cards/repository/CardRepository.java | 2 +- 7 files changed, 381 insertions(+), 6 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 54ce90f5586..dcc8f7e0ce6 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -97,6 +97,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardSelectorScrollPane.setOpaque(false); cardSelectorScrollPane.getViewport().setOpaque(false); + + taggerScrollPane.setOpaque(false); + taggerScrollPane.getViewport().setOpaque(false); + cbSortBy.setModel(new DefaultComboBoxModel<>(SortBy.values())); cbSortBy.setSelectedItem(sortSetting.getSortBy()); jTextFieldSearch.addActionListener(searchAction); @@ -106,11 +110,15 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene tbColor.setOpaque(true); // false = transparent tbTypes.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); tbTypes.setOpaque(true); // false = transparent + taggerScrollPane.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); + taggerScrollPane.setOpaque(true); cardSelectorBottomPanel.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); cardSelectorBottomPanel.setOpaque(true); // false = transparent } private void initListViewComponents() { + taggerTable = new JTable(); + mainTable = new JTable(); mainModel = new TableModel(); @@ -136,6 +144,11 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene // mainTable.setToolTipText(cardSelectorScrollPane.getToolTipText()); cardSelectorScrollPane.setViewportView(mainTable); + + taggerScrollPane.setViewportView(taggerTable); + + taggerTable.setOpaque(false); + mainTable.setOpaque(false); cbSortBy.setEnabled(false); chkPiles.setEnabled(false); @@ -630,6 +643,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene jButtonClean = new javax.swing.JButton(); cardCountLabel = new javax.swing.JLabel(); cardCount = new javax.swing.JLabel(); + + taggerScrollPane = new javax.swing.JScrollPane(); + tablePanel = new javax.swing.JSplitPane(JSplitPane.HORIZONTAL_SPLIT, cardSelectorScrollPane, taggerScrollPane); tbColor.setFloatable(false); tbColor.setRollover(true); @@ -1105,6 +1121,8 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardSelectorBottomPanel.setOpaque(false); cardSelectorBottomPanel.setPreferredSize(new java.awt.Dimension(897, 40)); + tablePanel.setOpaque(false); + tablePanel.setPreferredSize(new java.awt.Dimension(600, 40)); jButtonAddToMain.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/deck_in.png"))); // NOI18N jButtonAddToMain.setToolTipText("Add selected cards to deck.
\nAlternative: Double click the card in card selector to move a card to the deck."); @@ -1248,8 +1266,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardCountLabel.setToolTipText("Number of cards currently shown."); cardCount.setText("0"); - + javax.swing.GroupLayout cardSelectorBottomPanelLayout = new javax.swing.GroupLayout(cardSelectorBottomPanel); + cardSelectorBottomPanel.setLayout(cardSelectorBottomPanelLayout); cardSelectorBottomPanelLayout.setHorizontalGroup( cardSelectorBottomPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1319,9 +1338,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(tbColor, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(tbTypes, javax.swing.GroupLayout.DEFAULT_SIZE, 1057, Short.MAX_VALUE) - .addComponent(cardSelectorScrollPane) + .addComponent(tablePanel) .addComponent(cardSelectorBottomPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1057, Short.MAX_VALUE) ); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -1329,7 +1349,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene .addGap(0, 0, 0) .addComponent(tbTypes, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) - .addComponent(cardSelectorScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE) + .addComponent(tablePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE) .addGap(0, 0, 0) .addComponent(cardSelectorBottomPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)) ); @@ -1690,6 +1710,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private TableModel mainModel; private JTable mainTable; + private JTable taggerTable; private ICardGrid currentView; private final CheckBoxList listCodeSelected; @@ -1751,6 +1772,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JToolBar tbTypes; private javax.swing.JToggleButton tbUncommon; private javax.swing.JToggleButton tbWhite; + + private javax.swing.JScrollPane taggerScrollPane; + private javax.swing.JSplitPane tablePanel; + // End of variables declaration//GEN-END:variables private final mage.client.cards.CardGrid cardGrid; // grid for piles view mode (example: selected cards in drafting) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java new file mode 100644 index 00000000000..04b87f91111 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java @@ -0,0 +1,344 @@ +package mage.client.deckeditor.table; + +import mage.client.MageFrame; +import mage.client.cards.BigCard; +import mage.client.cards.CardEventSource; +import mage.client.cards.ICardGrid; +import mage.client.deckeditor.SortSetting; +import mage.client.plugins.impl.Plugins; +import mage.client.util.ClientDefaultSettings; +import mage.client.util.ClientEventType; +import mage.client.util.Event; +import mage.client.util.Listener; +import mage.client.util.gui.GuiDisplayUtil; +import mage.constants.EnlargeMode; +import mage.cards.RateCard; +import mage.view.CardView; +import mage.view.CardsView; +import org.apache.log4j.Logger; +import org.jdesktop.swingx.JXPanel; +import org.mage.card.arcane.ManaSymbols; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumnModel; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.*; +import java.util.Map.Entry; + +/** + * Table Model for card list. + * + * @author nantuko + */ +public class TaggerModel extends AbstractTableModel { + + private static final long serialVersionUID = -528008802935423048L; + + private static final Logger log = Logger.getLogger(TableModel.class); + + protected final CardEventSource cardEventSource = new CardEventSource(); + protected BigCard bigCard; + protected UUID gameId; + private final Map cards = new LinkedHashMap<>(); + private final Map cardsNoCopies = new LinkedHashMap<>(); + private final List view = new ArrayList<>(); + private Dimension cardDimension; + + private boolean displayNoCopies = false; + private UpdateCountsCallback updateCountsCallback; + + private final String[] column = {"Qty", "Name", "Cost", "Color", "Type", "Stats", "Rarity", "Set", "Card number", "Draft Rating", "Color Identity"}; + public final int COLUMN_INDEX_COST = 2; + public final int COLUMN_INDEX_RATING = 9; + public final int COLUMN_INDEX_COLOR_IDENTITY = 10; + + private SortSetting sortSetting; + private int recentSortedColumn; + private boolean recentAscending; + + private boolean numberEditable; + + public TaggerModel() { + this.numberEditable = false; + } + + public void clear() { + this.clearCardEventListeners(); + this.clearCards(); + this.view.clear(); + } + + + public void clearCards() { + view.clear(); + cards.clear(); + } + + @Override + public int getRowCount() { + return view.size(); + } + + @Override + public int getColumnCount() { + return column.length; + } + + @Override + public String getColumnName(int n) { + return column[n]; + } + + @Override + public Object getValueAt(int row, int column) { + return getColumn(view.get(row), column); + } + + private Object getColumn(Object obj, int column) { + CardView c = (CardView) obj; + switch (column) { + case 0: + if (displayNoCopies) { + String key = c.getName() + c.getExpansionSetCode() + c.getCardNumber(); + Integer count = cardsNoCopies.get(key); + return count != null ? count : ""; + } + return ""; + case 1: + return c.getDisplayFullName(); // show full name in deck editor table, e.g. adventure with spell name + case 2: + // new svg images version + return ManaSymbols.getClearManaCost(c.getManaCostStr()); + /* + // old html images version + String manaCost = ""; + for (String m : c.getManaCost()) { + manaCost += m; + } + String castingCost = UI.getDisplayManaCost(manaCost); + castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, ManaSymbols.Type.TABLE); + return "" + castingCost + ""; + return castingCost; + */ + case 3: + return c.getColorText(); + case 4: + return c.getTypeText(); + case 5: + return c.isCreature() ? c.getPower() + '/' + + c.getToughness() : "-"; + case 6: + return c.getRarity() == null ? "" : c.getRarity().toString(); + case 7: + return c.getExpansionSetCode(); + case 8: + return c.getCardNumber(); + case 9: + return RateCard.rateCard(c, Collections.emptyList()); + case 10: + return ManaSymbols.getClearManaCost(c.getOriginalColorIdentity()); + default: + return "error"; + } + } + + private void addCard(CardView card, BigCard bigCard, UUID gameId) { + if (cardDimension == null) { + cardDimension = new Dimension(ClientDefaultSettings.dimensions.getFrameWidth(), + ClientDefaultSettings.dimensions.getFrameHeight()); + } + cards.put(card.getId(), card); + + if (displayNoCopies) { + String key = card.getName() + card.getExpansionSetCode() + card.getCardNumber(); + Integer count = 1; + if (cardsNoCopies.containsKey(key)) { + count = cardsNoCopies.get(key) + 1; + } else { + view.add(card); + } + cardsNoCopies.put(key, count); + } else { + view.add(card); + } + } + + @Override + public void drawCards(SortSetting sortSetting) { + fireTableDataChanged(); + } + + public void removeCard(UUID cardId) { + cards.remove(cardId); + view.removeIf(cardView -> cardView.getId().equals(cardId)); + } + + @Override + public void addCardEventListener(Listener listener) { + cardEventSource.addListener(listener); + } + + @Override + public void clearCardEventListeners() { + cardEventSource.clearListeners(); + } + + public void setNumber(int index, int number) { + CardView card = view.get(index); + cardEventSource.fireEvent(card, ClientEventType.SET_NUMBER, number); + } + + public void doubleClick(int index, MouseEvent e, boolean forceFakeAltDown) { + CardView card = view.get(index); + cardEventSource.fireEvent(card, ClientEventType.CARD_DOUBLE_CLICK, e, forceFakeAltDown); + } + + public void removeFromMainEvent(int index) { + cardEventSource.fireEvent(ClientEventType.DECK_REMOVE_SELECTION_MAIN); + } + + public void removeFromSideEvent(int index) { + cardEventSource.fireEvent(ClientEventType.DECK_REMOVE_SELECTION_SIDEBOARD); + } + + public void addListeners(final JTable table) { + // updates card detail, listens to any key strokes + + table.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent ev) { + } + + @Override + public void keyTyped(KeyEvent ev) { + } + + @Override + public void keyReleased(KeyEvent ev) { + int row = table.getSelectedRow(); + if (row != -1) { + showImage(row); + } + } + }); + + // updates card detail, listens to any mouse clicks + table.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (!SwingUtilities.isLeftMouseButton(e)) { + return; + } + int row = table.getSelectedRow(); + if (row != -1) { + showImage(row); + } + } + }); + + // sorts + MouseListener mouse = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (!SwingUtilities.isLeftMouseButton(e)) { + return; + } + TableColumnModel columnModel = table.getColumnModel(); + int viewColumn = columnModel.getColumnIndexAtX(e.getX()); + int column = table.convertColumnIndexToModel(viewColumn); + + if (column != -1) { + // sort ascending + boolean asc = true; + if (recentSortedColumn == column) { + asc = !recentAscending; + } + sortSetting.setSortIndex(column); + sortSetting.setAscending(asc); + sort(column, asc); + fireTableDataChanged(); + } + } + }; + table.getTableHeader().addMouseListener(mouse); + } + + private void showImage(int row) { + CardView card = view.get(row); + if (!card.getId().equals(bigCard.getCardId())) { + if (!MageFrame.isLite()) { + Image image = Plugins.instance.getOriginalImage(card); + if (image instanceof BufferedImage) { + // XXX: scaled to fit width + bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, new ArrayList<>(), false); + } else { + drawCardText(card); + } + } else { + drawCardText(card); + } + } + } + + private void drawCardText(CardView card) { + JXPanel panel = GuiDisplayUtil.getDescription(card, bigCard.getWidth(), bigCard.getHeight()); + panel.setVisible(true); + bigCard.hideTextComponent(); + bigCard.addJXPanel(card.getId(), panel); + } + + public List getCardsView() { + return view; + } + + public boolean sort(int column, boolean ascending) { + // used by addCard() to resort the cards + recentSortedColumn = column; + recentAscending = ascending; + + MageCardComparator sorter = new MageCardComparator(column, ascending); + view.sort(sorter); + + fireTableDataChanged(); + + return true; + } + + public int getRecentSortedColumn() { + return recentSortedColumn; + } + + public boolean isRecentAscending() { + return recentAscending; + } + + public void setDisplayNoCopies(boolean value) { + this.displayNoCopies = value; + } + + public void setUpdateCountsCallback(UpdateCountsCallback callback) { + this.updateCountsCallback = callback; + } + + public void setNumberEditable(boolean numberEditable) { + this.numberEditable = numberEditable; + } + + @Override + public int cardsSize() { + return cards.size(); + } + + @Override + public boolean isCellEditable(int row, int col) { + if (numberEditable && col == 0) { + return true; + } + return super.isCellEditable(row, col); + } + +} diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 0cb2e6e4242..2068250f33a 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -893,7 +893,7 @@ public class TablesPanel extends javax.swing.JPanel { formatFilterList.add(RowFilter.regexFilter("^Oathbreaker", TablesTableModel.COLUMN_DECK_TYPE)); } if (btnFormatLimited.isSelected()) { - formatFilterList.add(RowFilter.regexFilter("^Limited", TablesTableModel.COLUMN_DECK_TYPE)); + formatFilterList.add(RowFilter.regexFilter("^(?:(?:Unl)|L)imited", TablesTableModel.COLUMN_DECK_TYPE)); } if (btnFormatOther.isSelected()) { formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform|^Constructed - Freeform Unlimited|^Australian Highlander|^European Highlander|^Canadian Highlander|^Constructed - Old|^Constructed - Historic", TablesTableModel.COLUMN_DECK_TYPE)); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java index b07e82734eb..e33faeceaf2 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java @@ -32,7 +32,7 @@ public class ScryfallApiCard { transient public String imageLarge = ""; // potentially interesting fields, can be used in other places - //public UUID oracle_id; // TODO: implement card hint with oracle/cr ruling texts (see Rulings bulk data) + public String oracle_id; // TODO: implement card hint with oracle/cr ruling texts (see Rulings bulk data) //public Integer edhrec_rank; // TODO: use it to rating cards for AI and draft bots //public Object legalities; // TODO: add verify check for bans list //public Boolean full_art; // TODO: add verify check for full art usage in sets diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index e104610ca62..00ff1b9fd30 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -6,6 +6,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import mage.MageException; +import mage.cards.repository.CardRepository; import mage.client.remote.XmageURLConnection; import mage.client.util.CardLanguage; import mage.util.JsonUtil; @@ -485,6 +486,9 @@ public class ScryfallImageSource implements CardImageSource { continue; } + // I LOVE POTENTIAL SQL INJECTION!!!!! + CardRepository.instance.execSQL("UPDATE card SET oracleId = '"+ card.oracle_id +"' WHERE setCode = '"+ card.set +"' AND cardNumber = '"+ card.collector_number +"'"); + // keep only usefully languages // memory optimization: fewer items, from 470 MB to 96 MB diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index e9b8a2aab24..622df93e12a 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -40,6 +40,8 @@ public class CardInfo { protected String setCode; @DatabaseField(indexName = "setCode_cardNumber_index") protected String cardNumber; + @DatabaseField(indexName = "oracleId_index", canBeNull = true) + protected String oracleId; /** * Fast access to numerical card number (number without prefix/postfix: 123b -> 123) */ diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 9f6e39d6a75..d4742bbaef6 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -38,7 +38,7 @@ public enum CardRepository { // TODO: delete db version from cards and expansions due un-used (cause dbs re-created on each update now) private static final String VERSION_ENTITY_NAME = "card"; - private static final long CARD_DB_VERSION = 54; // raise this if db structure was changed + private static final long CARD_DB_VERSION = 55; // raise this if db structure was changed private static final long CARD_CONTENT_VERSION = 241; // raise this if new cards were added to the server private Dao cardsDao;