Merge branch 'editions-type-review' into 'master'

ReNEWED Edition Types and Dates for Card Edition, ReNewed `Set` filter dialog panel in Desktop, NEW `Blocks` Card|Deck Catalog Filter, Updates to Formats

See merge request core-developers/forge!4878
This commit is contained in:
Michael Kamensky
2021-07-06 04:23:24 +00:00
384 changed files with 1958 additions and 994 deletions

View File

@@ -1,20 +1,12 @@
package forge; package forge;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.card.CardDb; import forge.card.*;
import forge.card.CardDb.CardRequest; import forge.card.CardDb.CardRequest;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.card.PrintSheet;
import forge.item.BoosterBox; import forge.item.BoosterBox;
import forge.item.FatPack; import forge.item.FatPack;
import forge.item.PaperCard; import forge.item.PaperCard;
@@ -75,7 +67,7 @@ public class StaticData {
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder))); this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
this.blockDataFolder = blockDataFolder; this.blockDataFolder = blockDataFolder;
this.customCardReader = customCardReader; this.customCardReader = customCardReader;
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder))); this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder), true));
this.prefferedArt = prefferedArt; this.prefferedArt = prefferedArt;
lastInstance = this; lastInstance = this;
List<String> funnyCards = new ArrayList<>(); List<String> funnyCards = new ArrayList<>();
@@ -174,6 +166,22 @@ public class StaticData {
return sortedEditions; return sortedEditions;
} }
private TreeMap<CardEdition.Type, List<CardEdition>> editionsTypeMap;
public final Map<CardEdition.Type, List<CardEdition>> getEditionsTypeMap(){
if (editionsTypeMap == null){
editionsTypeMap = new TreeMap<>();
for (CardEdition.Type editionType : CardEdition.Type.values()){
editionsTypeMap.put(editionType, new ArrayList<>());
}
for (CardEdition edition : this.getSortedEditions()){
CardEdition.Type key = edition.getType();
List<CardEdition> editionsOfType = editionsTypeMap.get(key);
editionsOfType.add(edition);
}
}
return editionsTypeMap;
}
public CardEdition getCardEdition(String setCode){ public CardEdition getCardEdition(String setCode){
CardEdition edition = this.editions.get(setCode); CardEdition edition = this.editions.get(setCode);
if (edition == null) // try custom editions if (edition == null) // try custom editions

View File

@@ -629,7 +629,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} catch (Exception ex) { } catch (Exception ex) {
return false; return false;
} }
return edition != null && edition.getType() != Type.PROMOS; return edition != null && edition.getType() != Type.PROMO;
} }
})); }));
} }
@@ -641,7 +641,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
CardEdition edition = null; CardEdition edition = null;
try { try {
edition = editions.getEditionByCodeOrThrow(paperCard.getEdition()); edition = editions.getEditionByCodeOrThrow(paperCard.getEdition());
if (edition.getType() == Type.PROMOS||edition.getType() == Type.REPRINT) if (edition.getType() == Type.PROMO||edition.getType() == Type.REPRINT)
return false; return false;
} catch (Exception ex) { } catch (Exception ex) {
return false; return false;

View File

@@ -37,6 +37,7 @@ import java.util.TreeMap;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import forge.util.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Function; import com.google.common.base.Function;
@@ -52,11 +53,6 @@ import forge.card.CardDb.SetPreference;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.item.SealedProduct; import forge.item.SealedProduct;
import forge.util.Aggregates;
import forge.util.FileSection;
import forge.util.FileUtil;
import forge.util.IItemReader;
import forge.util.MyRandom;
import forge.util.storage.StorageBase; import forge.util.storage.StorageBase;
import forge.util.storage.StorageReaderBase; import forge.util.storage.StorageReaderBase;
import forge.util.storage.StorageReaderFolder; import forge.util.storage.StorageReaderFolder;
@@ -78,19 +74,23 @@ public final class CardEdition implements Comparable<CardEdition> {
CORE, CORE,
EXPANSION, EXPANSION,
REPRINT,
ONLINE,
STARTER, STARTER,
REPRINT,
BOXED_SET,
DUEL_DECKS, COLLECTOR_EDITION,
PREMIUM_DECK_SERIES, DUEL_DECK,
FROM_THE_VAULT, PROMO,
ONLINE,
OTHER, DRAFT,
PROMOS,
COMMANDER,
MULTIPLAYER,
FUNNY, FUNNY,
THIRDPARTY; // custom sets
OTHER, // FALLBACK CATEGORY
CUSTOM_SET; // custom sets
public String getBoosterBoxDefault() { public String getBoosterBoxDefault() {
switch (this) { switch (this) {
@@ -101,6 +101,19 @@ public final class CardEdition implements Comparable<CardEdition> {
return "0"; return "0";
} }
} }
public String toString(){
String[] names = TextUtil.splitWithParenthesis(this.name().toLowerCase(), '_');
for (int i = 0; i < names.length; i++)
names[i] = TextUtil.capitalize(names[i]);
return TextUtil.join(Arrays.asList(names), " ");
}
public static Type fromString(String label){
List<String> names = Arrays.asList(TextUtil.splitWithParenthesis(label.toUpperCase(), ' '));
String value = TextUtil.join(names, "_");
return Type.valueOf(value);
}
} }
public enum FoilType { public enum FoilType {
@@ -476,8 +489,16 @@ public final class CardEdition implements Comparable<CardEdition> {
} }
public static class Reader extends StorageReaderFolder<CardEdition> { public static class Reader extends StorageReaderFolder<CardEdition> {
private boolean isCustomEditions;
public Reader(File path) { public Reader(File path) {
super(path, CardEdition.FN_GET_CODE); super(path, CardEdition.FN_GET_CODE);
this.isCustomEditions = false;
}
public Reader(File path, boolean isCustomEditions) {
super(path, CardEdition.FN_GET_CODE);
this.isCustomEditions = isCustomEditions;
} }
@Override @Override
@@ -596,8 +617,11 @@ public final class CardEdition implements Comparable<CardEdition> {
res.alias = section.get("alias"); res.alias = section.get("alias");
res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH)); res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH));
String type = section.get("type");
Type enumType = Type.UNKNOWN; Type enumType = Type.UNKNOWN;
if (this.isCustomEditions){
enumType = Type.CUSTOM_SET; // Forcing ThirdParty Edition Type to avoid inconsistencies
} else {
String type = section.get("type");
if (null != type && !type.isEmpty()) { if (null != type && !type.isEmpty()) {
try { try {
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH)); enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH));
@@ -606,6 +630,8 @@ public final class CardEdition implements Comparable<CardEdition> {
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type); System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type);
} }
} }
}
res.type = enumType; res.type = enumType;
res.prerelease = section.get("Prerelease", null); res.prerelease = section.get("Prerelease", null);
res.boosterBoxCount = Integer.parseInt(section.get("BoosterBox", enumType.getBoosterBoxDefault())); res.boosterBoxCount = Integer.parseInt(section.get("BoosterBox", enumType.getBoosterBoxDefault()));

View File

@@ -57,8 +57,28 @@ import forge.util.storage.StorageReaderRecursiveFolderWithUserFolder;
public class GameFormat implements Comparable<GameFormat> { public class GameFormat implements Comparable<GameFormat> {
private final String name; private final String name;
public enum FormatType {Sanctioned, Casual, Historic, Digital, Custom} public enum FormatType {
public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Arena, Custom} SANCTIONED,
CASUAL,
HISTORIC,
DIGITAL,
CUSTOM
}
public enum FormatSubType {
BLOCK,
STANDARD,
EXTENDED,
PIONEER,
MODERN,
LEGACY,
VINTAGE,
COMMANDER,
PLANECHASE,
VIDEOGAME,
MTGO,
ARENA,
CUSTOM
}
// contains allowed sets, when empty allows all sets // contains allowed sets, when empty allows all sets
private FormatType formatType; private FormatType formatType;
@@ -85,11 +105,11 @@ public class GameFormat implements Comparable<GameFormat> {
private final int index; private final int index;
public GameFormat(final String fName, final Iterable<String> sets, final List<String> bannedCards) { public GameFormat(final String fName, final Iterable<String> sets, final List<String> bannedCards) {
this(fName, parseDate(DEFAULTDATE), sets, bannedCards, null, false, null, null, 0, FormatType.Custom, FormatSubType.Custom); this(fName, parseDate(DEFAULTDATE), sets, bannedCards, null, false, null, null, 0, FormatType.CUSTOM, FormatSubType.CUSTOM);
} }
public static final GameFormat NoFormat = new GameFormat("(none)", parseDate(DEFAULTDATE) , null, null, null, false public static final GameFormat NoFormat = new GameFormat("(none)", parseDate(DEFAULTDATE) , null, null, null, false
, null, null, Integer.MAX_VALUE, FormatType.Custom, FormatSubType.Custom); , null, null, Integer.MAX_VALUE, FormatType.CUSTOM, FormatSubType.CUSTOM);
public GameFormat(final String fName, final Date effectiveDate, final Iterable<String> sets, final List<String> bannedCards, public GameFormat(final String fName, final Date effectiveDate, final Iterable<String> sets, final List<String> bannedCards,
final List<String> restrictedCards, Boolean restrictedLegendary, final List<String> additionalCards, final List<String> restrictedCards, Boolean restrictedLegendary, final List<String> additionalCards,
@@ -276,6 +296,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (null == other) { if (null == other) {
return 1; return 1;
} }
if (other.formatType != formatType){ if (other.formatType != formatType){
return formatType.compareTo(other.formatType); return formatType.compareTo(other.formatType);
}else{ }else{
@@ -283,10 +304,11 @@ public class GameFormat implements Comparable<GameFormat> {
return formatSubType.compareTo(other.formatSubType); return formatSubType.compareTo(other.formatSubType);
} }
} }
if (formatType.equals(FormatType.Historic)){ if (formatType.equals(FormatType.HISTORIC)){
if(effectiveDate!=other.effectiveDate) {//for matching dates or default dates default to name sorting int compareDates = this.effectiveDate.compareTo(other.effectiveDate);
return other.effectiveDate.compareTo(effectiveDate); if (compareDates != 0)
} return compareDates;
return (this.index - other.index);
} }
return name.compareTo(other.name); return name.compareTo(other.name);
//return index - other.index; //return index - other.index;
@@ -338,15 +360,15 @@ public class GameFormat implements Comparable<GameFormat> {
String title = section.get("name"); String title = section.get("name");
FormatType formatType; FormatType formatType;
try { try {
formatType = FormatType.valueOf(section.get("type")); formatType = FormatType.valueOf(section.get("type").toUpperCase());
} catch (Exception e) { } catch (Exception e) {
formatType = FormatType.Custom; formatType = FormatType.CUSTOM;
} }
FormatSubType formatsubType; FormatSubType formatsubType;
try { try {
formatsubType = FormatSubType.valueOf(section.get("subtype")); formatsubType = FormatSubType.valueOf(section.get("subtype").toUpperCase());
} catch (Exception e) { } catch (Exception e) {
formatsubType = FormatSubType.Custom; formatsubType = FormatSubType.CUSTOM;
} }
Integer idx = section.getInt("order"); Integer idx = section.getInt("order");
String dateStr = section.get("effective"); String dateStr = section.get("effective");
@@ -432,7 +454,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getSanctionedList() { public Iterable<GameFormat> getSanctionedList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(format.getFormatType().equals(FormatType.Sanctioned)){ if(format.getFormatType().equals(FormatType.SANCTIONED)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -442,8 +464,8 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getFilterList() { public Iterable<GameFormat> getFilterList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(!format.getFormatType().equals(FormatType.Historic) if(!format.getFormatType().equals(FormatType.HISTORIC)
&&!format.getFormatType().equals(FormatType.Digital)){ &&!format.getFormatType().equals(FormatType.DIGITAL)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -453,7 +475,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getHistoricList() { public Iterable<GameFormat> getHistoricList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(format.getFormatType().equals(FormatType.Historic)){ if(format.getFormatType().equals(FormatType.HISTORIC)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -463,7 +485,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Map<String, List<GameFormat>> getHistoricMap() { public Map<String, List<GameFormat>> getHistoricMap() {
Map<String, List<GameFormat>> coreList = new HashMap<>(); Map<String, List<GameFormat>> coreList = new HashMap<>();
for(GameFormat format: naturallyOrdered){ for(GameFormat format: naturallyOrdered){
if(format.getFormatType().equals(FormatType.Historic)){ if(format.getFormatType().equals(FormatType.HISTORIC)){
String alpha = format.getName().substring(0,1); String alpha = format.getName().substring(0,1);
if(!coreList.containsKey(alpha)){ if(!coreList.containsKey(alpha)){
coreList.put(alpha,new ArrayList<>()); coreList.put(alpha,new ArrayList<>());
@@ -528,15 +550,15 @@ public class GameFormat implements Comparable<GameFormat> {
Set<FormatSubType> coveredTypes = new HashSet<>(); Set<FormatSubType> coveredTypes = new HashSet<>();
CardPool allCards = deck.getAllCardsInASinglePool(); CardPool allCards = deck.getAllCardsInASinglePool();
for(GameFormat gf : reverseDateOrdered) { for(GameFormat gf : reverseDateOrdered) {
if (gf.getFormatType().equals(FormatType.Digital) && !exhaustive){ if (gf.getFormatType().equals(FormatType.DIGITAL) && !exhaustive){
//exclude Digital formats from lists for now //exclude Digital formats from lists for now
continue; continue;
} }
if (gf.getFormatSubType().equals(FormatSubType.Commander)){ if (gf.getFormatSubType().equals(FormatSubType.COMMANDER)){
//exclude Commander format as other deck checks are not performed here //exclude Commander format as other deck checks are not performed here
continue; continue;
} }
if (gf.getFormatType().equals(FormatType.Historic) && coveredTypes.contains(gf.getFormatSubType()) if (gf.getFormatType().equals(FormatType.HISTORIC) && coveredTypes.contains(gf.getFormatSubType())
&& !exhaustive){ && !exhaustive){
//exclude duplicate formats - only keep first of e.g. Standard historical //exclude duplicate formats - only keep first of e.g. Standard historical
continue; continue;
@@ -570,7 +592,7 @@ public class GameFormat implements Comparable<GameFormat> {
return gf1.formatSubType.compareTo(gf2.formatSubType); return gf1.formatSubType.compareTo(gf2.formatSubType);
} }
} }
if (gf1.formatType.equals(FormatType.Historic)){ if (gf1.formatType.equals(FormatType.HISTORIC)){
if(gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting if(gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting
return gf1.effectiveDate.compareTo(gf2.effectiveDate); return gf1.effectiveDate.compareTo(gf2.effectiveDate);
} }

View File

@@ -1,5 +1,7 @@
package forge.itemmanager; package forge.itemmanager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -11,21 +13,8 @@ import forge.gamemodes.quest.QuestWorld;
import forge.gamemodes.quest.data.QuestPreferences; import forge.gamemodes.quest.data.QuestPreferences;
import forge.gui.GuiUtils; import forge.gui.GuiUtils;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.itemmanager.filters.AdvancedSearchFilter; import forge.itemmanager.filters.*;
import forge.itemmanager.filters.CardCMCFilter; import forge.localinstance.properties.ForgePreferences;
import forge.itemmanager.filters.CardCMCRangeFilter;
import forge.itemmanager.filters.CardColorFilter;
import forge.itemmanager.filters.CardFoilFilter;
import forge.itemmanager.filters.CardFormatFilter;
import forge.itemmanager.filters.CardPowerFilter;
import forge.itemmanager.filters.CardQuestWorldFilter;
import forge.itemmanager.filters.CardRatingFilter;
import forge.itemmanager.filters.CardSearchFilter;
import forge.itemmanager.filters.CardSetFilter;
import forge.itemmanager.filters.CardToughnessFilter;
import forge.itemmanager.filters.CardTypeFilter;
import forge.itemmanager.filters.FormatFilter;
import forge.itemmanager.filters.ItemFilter;
import forge.model.FModel; import forge.model.FModel;
import forge.screens.home.quest.DialogChooseFormats; import forge.screens.home.quest.DialogChooseFormats;
import forge.screens.home.quest.DialogChooseSets; import forge.screens.home.quest.DialogChooseSets;
@@ -162,6 +151,28 @@ public class CardManager extends ItemManager<PaperCard> {
} }
menu.add(world); menu.add(world);
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.LOAD_HISTORIC_FORMATS)) {
JMenu blocks = GuiUtils.createMenu(localizer.getMessage("lblBlock"));
List<GameFormat> blockFormats = new ArrayList<>();
for (GameFormat format : FModel.getFormats().getHistoricList()){
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
continue;
if (!format.getName().endsWith("Block"))
continue;
blockFormats.add(format);
}
Collections.sort(blockFormats); // GameFormat will be sorted by Index!
for (final GameFormat f : blockFormats) {
GuiUtils.addMenuItem(blocks, f.getName(), null, new Runnable() {
@Override
public void run() {
itemManager.addFilter(new CardBlockFilter(itemManager, f));
}
}, CardBlockFilter.canAddCardBlockWorld(f, itemManager.getFilter(CardBlockFilter.class)));
}
menu.add(blocks);
}
GuiUtils.addSeparator(menu); GuiUtils.addSeparator(menu);
GuiUtils.addMenuItem(menu, localizer.getMessage("lblColors"), null, new Runnable() { GuiUtils.addMenuItem(menu, localizer.getMessage("lblColors"), null, new Runnable() {

View File

@@ -4,9 +4,7 @@ import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.HashMap; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.swing.JMenu; import javax.swing.JMenu;
@@ -14,6 +12,8 @@ import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import forge.itemmanager.filters.*;
import forge.localinstance.properties.ForgePreferences;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import forge.Singletons; import forge.Singletons;
@@ -29,15 +29,6 @@ import forge.gui.GuiUtils;
import forge.gui.UiCommand; import forge.gui.UiCommand;
import forge.gui.framework.FScreen; import forge.gui.framework.FScreen;
import forge.item.InventoryItem; import forge.item.InventoryItem;
import forge.itemmanager.filters.AdvancedSearchFilter;
import forge.itemmanager.filters.DeckColorFilter;
import forge.itemmanager.filters.DeckFolderFilter;
import forge.itemmanager.filters.DeckFormatFilter;
import forge.itemmanager.filters.DeckQuestWorldFilter;
import forge.itemmanager.filters.DeckSearchFilter;
import forge.itemmanager.filters.DeckSetFilter;
import forge.itemmanager.filters.FormatFilter;
import forge.itemmanager.filters.ItemFilter;
import forge.itemmanager.views.ItemCellRenderer; import forge.itemmanager.views.ItemCellRenderer;
import forge.itemmanager.views.ItemListView; import forge.itemmanager.views.ItemListView;
import forge.itemmanager.views.ItemTableColumn; import forge.itemmanager.views.ItemTableColumn;
@@ -123,7 +114,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
/** /**
* Sets the delete command. * Sets the delete command.
* *
* @param c0 &emsp; {@link forge.forge.gui.UiCommand} command executed on delete. * @param c0 &emsp; {@link forge.gui.UiCommand} command executed on delete.
*/ */
public void setDeleteCommand(final UiCommand c0) { public void setDeleteCommand(final UiCommand c0) {
this.cmdDelete = c0; this.cmdDelete = c0;
@@ -132,7 +123,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
/** /**
* Sets the select command. * Sets the select command.
* *
* @param c0 &emsp; {@link forge.forge.gui.UiCommand} command executed on row select. * @param c0 &emsp; {@link forge.gui.UiCommand} command executed on row select.
*/ */
public void setSelectCommand(final UiCommand c0) { public void setSelectCommand(final UiCommand c0) {
this.cmdSelect = c0; this.cmdSelect = c0;
@@ -289,6 +280,28 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
} }
menu.add(world); menu.add(world);
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.LOAD_HISTORIC_FORMATS)) {
JMenu blocks = GuiUtils.createMenu(localizer.getMessage("lblBlock"));
List<GameFormat> blockFormats = new ArrayList<>();
for (GameFormat format : FModel.getFormats().getHistoricList()){
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
continue;
if (!format.getName().endsWith("Block"))
continue;
blockFormats.add(format);
}
Collections.sort(blockFormats); // GameFormat will be sorted by Index!
for (final GameFormat f : blockFormats) {
GuiUtils.addMenuItem(blocks, f.getName(), null, new Runnable() {
@Override
public void run() {
addFilter(new DeckBlockFilter(DeckManager.this, f));
}
}, DeckBlockFilter.canAddCardBlock(f, getFilter(DeckBlockFilter.class)));
}
menu.add(blocks);
}
GuiUtils.addSeparator(menu); GuiUtils.addSeparator(menu);
GuiUtils.addMenuItem(menu, localizer.getMessage("lblColors"), null, new Runnable() { GuiUtils.addMenuItem(menu, localizer.getMessage("lblColors"), null, new Runnable() {

View File

@@ -138,11 +138,7 @@ public abstract class ItemManager<T extends InventoryItem> extends JPanel implem
protected boolean lockFiltering; protected boolean lockFiltering;
/** /**
* ItemManager Constructor. * ItemManager Constructor
*
* @param genericType0 the class of item that this table will contain
* @param statLabels0 stat labels for this item manager
* @param wantUnique0 whether this table should display only one item with the same name
*/ */
protected ItemManager(final Class<T> genericType0, final CDetailPicture cDetailPicture, final boolean wantUnique0) { protected ItemManager(final Class<T> genericType0, final CDetailPicture cDetailPicture, final boolean wantUnique0) {
this.cDetailPicture = cDetailPicture; this.cDetailPicture = cDetailPicture;
@@ -1043,9 +1039,7 @@ public abstract class ItemManager<T extends InventoryItem> extends JPanel implem
/** /**
* *
* updateView. * updateView
*
* @param bForceFilter
*/ */
public void updateView(final boolean forceFilter, final Iterable<T> itemsToSelect) { public void updateView(final boolean forceFilter, final Iterable<T> itemsToSelect) {
final boolean useFilter = (forceFilter && (this.filterPredicate != null)) || !isUnfiltered(); final boolean useFilter = (forceFilter && (this.filterPredicate != null)) || !isUnfiltered();

View File

@@ -0,0 +1,83 @@
package forge.itemmanager.filters;
import forge.game.GameFormat;
import forge.item.PaperCard;
import forge.itemmanager.ItemManager;
import forge.model.FModel;
import java.util.*;
public class CardBlockFilter extends CardSetFilter {
private final Set<GameFormat> selectedBlocks = new HashSet<>();
private GameFormat cardBlock;
public CardBlockFilter(final ItemManager<? super PaperCard> itemManager0, final GameFormat cardBlock) {
super(itemManager0, cardBlock.getAllowedSetCodes(), false);
this.formats.add(cardBlock);
this.cardBlock = cardBlock;
this.selectedBlocks.add(cardBlock);
}
private CardBlockFilter(final ItemManager<? super PaperCard> itemManager0, GameFormat cardBlock,
Set<GameFormat> selectedBlocks){
this(itemManager0, cardBlock);
this.selectedBlocks.addAll(selectedBlocks);
}
@Override
public ItemFilter<PaperCard> createCopy() {
return new CardBlockFilter(itemManager, this.cardBlock, this.selectedBlocks);
}
public static boolean canAddCardBlockWorld(final GameFormat cardBlock, final ItemFilter<PaperCard> existingFilter) {
if (cardBlock == null || FModel.getBlocks() == null) {
return false; //must have format
}
return existingFilter == null || !((CardBlockFilter)existingFilter).selectedBlocks.contains(cardBlock);
}
/**
* Merge the given filter with this filter if possible
* @param filter
* @return true if filter merged in or to suppress adding a new filter, false to allow adding new filter
*/
@Override
public boolean merge(final ItemFilter<?> filter) {
final CardBlockFilter cardBlockFilter = (CardBlockFilter)filter;
this.selectedBlocks.addAll(cardBlockFilter.selectedBlocks);
this.sets.addAll(cardBlockFilter.sets);
List<String> allBannedCards = new ArrayList<>();
for (GameFormat f : this.formats){
List<String> bannedCards = f.getBannedCardNames();
if (bannedCards != null && bannedCards.size() > 0)
allBannedCards.addAll(bannedCards);
}
this.formats.clear();
this.formats.add(new GameFormat(null, this.sets, allBannedCards));
return true;
}
@Override
protected String getCaption() {
return "Block";
}
@Override
protected int getCount() {
int setCount = 0;
for (GameFormat block : this.selectedBlocks)
setCount += block.getAllowedSetCodes().size();
return setCount;
}
@Override
protected Iterable<String> getList() {
final Set<String> strings = new HashSet<>();
for (final GameFormat f : this.selectedBlocks) {
strings.add(f.getName());
}
return strings;
}
}

View File

@@ -14,12 +14,21 @@ public class CardQuestWorldFilter extends CardFormatFilter {
private final Set<QuestWorld> questWorlds = new HashSet<>(); private final Set<QuestWorld> questWorlds = new HashSet<>();
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0) { public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0) {
this(itemManager0, true);
}
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, boolean allowReprints) {
super(itemManager0); super(itemManager0);
this.allowReprints = allowReprints;
} }
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, final QuestWorld questWorld0) { public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, final QuestWorld questWorld0) {
this(itemManager0, questWorld0, true);
}
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, final QuestWorld questWorld0, boolean allowReprints){
super(itemManager0); super(itemManager0);
this.questWorlds.add(questWorld0); this.questWorlds.add(questWorld0);
this.formats.add(getQuestWorldFormat(questWorld0)); this.formats.add(getQuestWorldFormat(questWorld0));
this.allowReprints = allowReprints;
} }
@Override @Override

View File

@@ -14,7 +14,7 @@ import forge.screens.home.quest.DialogChooseSets;
* *
*/ */
public class CardSetFilter extends CardFormatFilter { public class CardSetFilter extends CardFormatFilter {
private final Set<String> sets = new HashSet<>(); protected final Set<String> sets = new HashSet<>();
public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, boolean allowReprints0) { public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, boolean allowReprints0) {
super(itemManager0); super(itemManager0);
@@ -50,9 +50,9 @@ public class CardSetFilter extends CardFormatFilter {
} }
public void edit(final ItemManager<? super PaperCard> itemManager) { public void edit(final ItemManager<? super PaperCard> itemManager) {
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true); final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true,
this.allowReprints);
final CardSetFilter itemFilter = this; final CardSetFilter itemFilter = this;
dialog.setWantReprintsCB(allowReprints);
dialog.setOkCallback(new Runnable() { dialog.setOkCallback(new Runnable() {
@Override @Override

View File

@@ -0,0 +1,86 @@
package forge.itemmanager.filters;
import forge.deck.DeckProxy;
import forge.game.GameFormat;
import forge.item.PaperCard;
import forge.itemmanager.ItemManager;
import forge.model.FModel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DeckBlockFilter extends DeckSetFilter {
private final Set<GameFormat> selectedBlocks = new HashSet<>();
private GameFormat cardBlock;
public DeckBlockFilter(ItemManager<? super DeckProxy> itemManager0, final GameFormat cardBlock) {
super(itemManager0, cardBlock.getAllowedSetCodes(), false);
this.formats.add(cardBlock);
this.selectedBlocks.add(cardBlock);
}
private DeckBlockFilter(ItemManager<? super DeckProxy> itemManager0, GameFormat cardBlock,
Set<GameFormat> selectedBlocks){
this(itemManager0, cardBlock);
this.selectedBlocks.addAll(selectedBlocks);
}
@Override
public ItemFilter<DeckProxy> createCopy() {
return new DeckBlockFilter(itemManager, this.cardBlock, this.selectedBlocks);
}
public static boolean canAddCardBlock(final GameFormat cardBlock, final ItemFilter<DeckProxy> existingFilter) {
if (cardBlock == null || FModel.getBlocks() == null) {
return false; //must have format
}
return existingFilter == null || !((DeckBlockFilter)existingFilter).selectedBlocks.contains(cardBlock);
}
/**
* Merge the given filter with this filter if possible
* @param filter
* @return true if filter merged in or to suppress adding a new filter, false to allow adding new filter
*/
@Override
public boolean merge(final ItemFilter<?> filter) {
final DeckBlockFilter cardBlockFilter = (DeckBlockFilter)filter;
this.selectedBlocks.addAll(cardBlockFilter.selectedBlocks);
this.sets.addAll(cardBlockFilter.sets);
List<String> allBannedCards = new ArrayList<>();
for (GameFormat f : this.formats){
List<String> bannedCards = f.getBannedCardNames();
if (bannedCards != null && bannedCards.size() > 0)
allBannedCards.addAll(bannedCards);
}
this.formats.clear();
this.formats.add(new GameFormat(null, this.sets, allBannedCards));
return true;
}
@Override
protected String getCaption() {
return "Block";
}
@Override
protected int getCount() {
int setCount = 0;
for (GameFormat block : this.selectedBlocks)
setCount += block.getAllowedSetCodes().size();
return setCount;
}
@Override
protected Iterable<String> getList() {
final Set<String> strings = new HashSet<>();
for (final GameFormat f : this.selectedBlocks) {
strings.add(f.getName());
}
return strings;
}
}

View File

@@ -25,7 +25,7 @@ public class DeckFormatFilter extends FormatFilter<DeckProxy> {
} }
@Override @Override
protected final Predicate<DeckProxy> buildPredicate() { protected Predicate<DeckProxy> buildPredicate() {
return DeckProxy.createPredicate(SFilterUtil.buildFormatFilter(this.formats, this.allowReprints)); return DeckProxy.createPredicate(SFilterUtil.buildFormatFilter(this.formats, this.allowReprints));
} }

View File

@@ -4,14 +4,16 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import com.google.common.base.Predicate;
import forge.deck.DeckProxy; import forge.deck.DeckProxy;
import forge.game.GameFormat; import forge.game.GameFormat;
import forge.itemmanager.ItemManager; import forge.itemmanager.ItemManager;
import forge.screens.home.quest.DialogChooseSets; import forge.screens.home.quest.DialogChooseSets;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
public class DeckSetFilter extends DeckFormatFilter { public class DeckSetFilter extends DeckFormatFilter {
private final Set<String> sets = new HashSet<>(); protected final Set<String> sets = new HashSet<>();
public DeckSetFilter(ItemManager<? super DeckProxy> itemManager0, Collection<String> sets0, boolean allowReprints0) { public DeckSetFilter(ItemManager<? super DeckProxy> itemManager0, Collection<String> sets0, boolean allowReprints0) {
super(itemManager0); super(itemManager0);
@@ -47,7 +49,8 @@ public class DeckSetFilter extends DeckFormatFilter {
} }
public void edit() { public void edit() {
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true); final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true,
this.allowReprints);
dialog.setOkCallback(new Runnable() { dialog.setOkCallback(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -74,4 +77,14 @@ public class DeckSetFilter extends DeckFormatFilter {
protected Iterable<String> getList() { protected Iterable<String> getList() {
return this.sets; return this.sets;
} }
@Override
protected Predicate<DeckProxy> buildPredicate() {
return new Predicate<DeckProxy>() {
@Override
public boolean apply(@NullableDecl DeckProxy input) {
return input != null && sets.contains(input.getEdition().getCode());
}
};
}
} }

View File

@@ -48,15 +48,15 @@ public class DialogChooseFormats {
FCheckBox box = new FCheckBox(format.getName()); FCheckBox box = new FCheckBox(format.getName());
box.setName(format.getName()); box.setName(format.getName());
switch (format.getFormatType()){ switch (format.getFormatType()){
case Sanctioned: case SANCTIONED:
sanctioned.add(box); sanctioned.add(box);
break; break;
case Historic: case HISTORIC:
historic.add(box); historic.add(box);
break; break;
case Custom: case CUSTOM:
case Casual: case CASUAL:
case Digital: case DIGITAL:
default: default:
casual.add(box); casual.add(box);
break; break;

View File

@@ -1,40 +1,25 @@
package forge.screens.home.quest; package forge.screens.home.quest;
import java.awt.Font; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.*;
import java.awt.event.ActionListener; import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import javax.swing.ButtonGroup; import javax.swing.*;
import javax.swing.JPanel; import javax.swing.event.ChangeEvent;
import javax.swing.JSeparator; import javax.swing.event.ChangeListener;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import com.google.common.collect.Lists; import forge.Singletons;
import forge.toolbox.*;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.game.GameFormat; import forge.game.GameFormat;
import forge.gui.SOverlayUtils; import forge.gui.SOverlayUtils;
import forge.localinstance.skin.FSkinProp; import forge.localinstance.skin.FSkinProp;
import forge.model.FModel; import forge.model.FModel;
import forge.toolbox.FButton;
import forge.toolbox.FCheckBox;
import forge.toolbox.FCheckBoxList;
import forge.toolbox.FLabel;
import forge.toolbox.FOverlay;
import forge.toolbox.FPanel;
import forge.toolbox.FRadioButton;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.toolbox.FTextField;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.TextUtil;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import forge.toolbox.FCheckBoxTree.FTreeNode;
import forge.toolbox.FCheckBoxTree.FTreeNodeData;
public class DialogChooseSets { public class DialogChooseSets {
@@ -42,312 +27,418 @@ public class DialogChooseSets {
private boolean wantReprints = true; private boolean wantReprints = true;
private Runnable okCallback; private Runnable okCallback;
private final List<FCheckBox> choices = new ArrayList<>();
private final FCheckBox cbWantReprints = new FCheckBox(Localizer.getInstance().getMessage("lblDisplayRecentSetReprints")); private final FCheckBox cbWantReprints = new FCheckBox(Localizer.getInstance().getMessage("lblDisplayRecentSetReprints"));
private final FCheckBoxTree checkBoxTree = new FCheckBoxTree();
// lists are of set codes (e.g. "2ED") public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets, boolean showWantReprintsCheckbox) { boolean showWantReprintsCheckbox) {
this(preselectedSets, unselectableSets, showWantReprintsCheckbox, false);
// create a local copy of the editions list so we can sort it
List<CardEdition> editions = Lists.newArrayList(FModel.getMagicDb().getEditions());
Collections.sort(editions);
Collections.reverse(editions);
List<CardEdition> customEditions = Lists.newArrayList(FModel.getMagicDb().getCustomEditions());
boolean customSetsExist = (customEditions.size() > 0);
if (customSetsExist){
Collections.sort(customEditions);
Collections.reverse(customEditions);
} }
List<FCheckBox> coreSets = new ArrayList<>();
List<FCheckBox> expansionSets = new ArrayList<>();
List<FCheckBox> otherSets = new ArrayList<>();
for (CardEdition ce : editions) { public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
boolean showWantReprintsCheckbox, boolean allowReprints) {
// get the map of each editions per type
Map<CardEdition.Type, List<CardEdition>> editionsTypeMap = FModel.getMagicDb().getEditionsTypeMap();
/* Gather all the different types among preselected and unselectable sets (if any)
lists are of set codes (e.g. "2ED", edition.getCode())
NOTE: preselected SetTypes will be created as TreeSet as the ordering of Types will be used to
decide which type in the UI list of types should be selected, in case of multiple types
available in preselected types (e.g. expansion and core, core will be selected as it comes first) */
Set<CardEdition.Type> preselectedTypes = null;
if (preselectedSets != null){
preselectedTypes = new TreeSet<>();
for (String code: preselectedSets){
CardEdition edition = FModel.getMagicDb().getCardEdition(code);
preselectedTypes.add(edition.getType());
}
}
TreeMap<FTreeNodeData, List<FTreeNodeData>> editionTypeTreeData = new TreeMap<>();
TreeMap<CardEdition.Type, Integer> allEditionTypes = new TreeMap<>();
List<CardEdition> allCardEditions = new ArrayList<>();
for (CardEdition.Type editionType : editionsTypeMap.keySet()) {
List<CardEdition> editionsOfType = editionsTypeMap.get(editionType);
if (editionsOfType.size() == 0) // skip empty set types
continue;
List<FTreeNodeData> editionPerTypeNodes = new ArrayList<>();
allCardEditions.addAll(editionsOfType);
int enabledEditionsOfTypeCounter = 0;
for (CardEdition ce: editionsOfType){
String code = ce.getCode(); String code = ce.getCode();
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code))); boolean isSelected = null != preselectedSets && preselectedSets.contains(code);
box.setName(code); boolean isEnabled = null == unselectableSets || !unselectableSets.contains(code);
box.setSelected(null != preselectedSets && preselectedSets.contains(code)); FTreeNodeData editionNode = new FTreeNodeData(ce, ce.getName(), ce.getCode());
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code)); editionNode.isEnabled = isEnabled;
switch (ce.getType()) { editionNode.isSelected = isSelected;
case CORE: if (isEnabled)
coreSets.add(box); enabledEditionsOfTypeCounter += 1;
break; editionPerTypeNodes.add(editionNode);
case EXPANSION: }
expansionSets.add(box); editionTypeTreeData.put(new FTreeNodeData(editionType), editionPerTypeNodes);
break; allEditionTypes.put(editionType, enabledEditionsOfTypeCounter);
default: }
otherSets.add(box); this.checkBoxTree.setTreeData(editionTypeTreeData);
// === 0. MAIN PANEL WINDOW ===
// ===================================================================
// Initialise UI
FPanel mainDialogPanel = new FPanel(new MigLayout(
String.format("insets 10, gap 5, center, wrap 2, w %d!", getMainDialogWidth())));
mainDialogPanel.setOpaque(false);
mainDialogPanel.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
// === 1. RANDOM SELECTION PANEL ===
// ===================================================================
JPanel randomSelectionPanel = new JPanel(new MigLayout("insets 10, gap 5, right, wrap 2"));
randomSelectionPanel.setOpaque(false);
// === 2. RANDOM OPTIONS PANEL ===
// Setup components for the random selection panel.
// NOTES: These components need to be defined first, as they will also be controlled by
// format selection buttons (enabled/disabled accordingly).
randomSelectionPanel.add(new FLabel.Builder().text(
Localizer.getInstance().getMessage("lblSelectRandomSets")).fontSize(14)
.fontStyle(Font.BOLD).build(), "h 40!, w 100%, center, span 2");
FButton randomSelectionButton = new FButton(Localizer.getInstance().getMessage("lblRandomizeSets"));
randomSelectionButton.setFont(FSkin.getBoldFont(13));
randomSelectionButton.setEnabled(false); // by default is not enabled
// === SPINNER AND LABELS ===
TreeMap<CardEdition.Type, FSpinner> spinnersEditionTypeMap = new TreeMap<>();
TreeMap<CardEdition.Type, FLabel> labelsEditionTypeMap = new TreeMap<>();
List<FSpinner> editionTypeSpinners = new ArrayList<>();
for (CardEdition.Type editionType: allEditionTypes.keySet()) {
int enabledEditionCount = allEditionTypes.get(editionType);
FSpinner spinner = new FSpinner.Builder().initialValue(0).minValue(0).maxValue(enabledEditionCount).build();
String labTxt = "<html>" + editionType.toString().replaceAll(" ", "<br>") + ": </html>";
FLabel label = new FLabel.Builder().text(labTxt).fontSize(13).build();
// Determine status of component
if (enabledEditionCount == 0) {
// No editions enabled meaning:
// the edition type HAS extensions but none of them is enabled!
spinner.setEnabled(false);
label.setEnabled(false);
}
editionTypeSpinners.add(spinner);
labelsEditionTypeMap.put(editionType, label);
spinnersEditionTypeMap.put(editionType, spinner);
}
// == SPINNERS ACTION PERFORMED ==
editionTypeSpinners.forEach(spinner -> {
spinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
// As soon as the value of a spinner becomes different from zero,
// enabled the random selection button.
int spinValue = (int) spinner.getValue();
if (spinValue > 0) {
if (!randomSelectionButton.isEnabled())
randomSelectionButton.setEnabled(true);
} else {
// Similarly, when all spinners are set to zero,
// disable the random selection button
boolean allZeros = true;
for (FSpinner spin : editionTypeSpinners) {
int value = (int) spin.getValue();
if (value != 0) {
allZeros = false;
break; break;
} }
} }
// CustomSet if (allZeros)
List<FCheckBox> customSets = new ArrayList<>(); randomSelectionButton.setEnabled(false);
if (customSetsExist) { }
for (CardEdition ce : customEditions){ }
String code = ce.getCode(); });
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code))); });
box.setName(code);
box.setSelected(null != preselectedSets && preselectedSets.contains(code)); // == ADD SPINNERS AND LABELS TO THE PANEL ==
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code)); JPanel typeFieldsPanel = null;
customSets.add(box); randomSelectionPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, center");
randomSelectionPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("nlSelectRandomSets"))
.fontSize(12).fontStyle(Font.ITALIC).build(), "w 80%!, h 22px!, gap 5 0 0 0, span 2, center");
String pairPanelLayout = "wrap 2, w 30%";
int componentIndex = 0;
int pairPerPanel = 3;
int panelCompsCount = 0;
for (CardEdition.Type editionType : allEditionTypes.keySet()) {
if (panelCompsCount == 0)
typeFieldsPanel = new JPanel(new MigLayout("insets 5, wrap 3"));
typeFieldsPanel.setOpaque(false);
JPanel pairPanel = new JPanel(new MigLayout(pairPanelLayout));
pairPanel.setOpaque(false);
pairPanel.add(labelsEditionTypeMap.get(editionType), "w 100!, align left, span 1");
pairPanel.add(spinnersEditionTypeMap.get(editionType), "w 45!, align right, span 1");
typeFieldsPanel.add(pairPanel, "span 1, center, growx, h 50!");
panelCompsCount += 1;
componentIndex += 1;
if ((panelCompsCount == pairPerPanel) || (componentIndex == editionTypeSpinners.size())) {
// add panel to outer container if we ran out of space, or we are processing the last item
randomSelectionPanel.add(typeFieldsPanel, "w 100%, span 2");
panelCompsCount = 0; // reset counter for the new panel, in case
}
}
randomSelectionPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, gap 0");
FButton clearSelectionButton = new FButton(Localizer.getInstance().getMessage("lblClearSelection"));
clearSelectionButton.setFont(FSkin.getBoldFont(13));
// == UPDATE RANDOM PANEL LAYOUT ==
randomSelectionPanel.add(clearSelectionButton, "gaptop 15, w 40%, h 26!, center");
randomSelectionPanel.add(randomSelectionButton, "gaptop 15, w 40%, h 26!, center");
if (showWantReprintsCheckbox) {
cbWantReprints.setSelected(allowReprints);
randomSelectionPanel.add(cbWantReprints, "gaptop 10, left, span, wrap");
} }
} // === 2. OPTIONS PANEL ===
int wrapCol = !customSetsExist ? 3 : 4; // ===================================================================
FPanel panel = new FPanel(new MigLayout(String.format("insets 10, gap 5, center, wrap %d", wrapCol)));
panel.setOpaque(false);
panel.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
FTextField coreField = new FTextField.Builder().text("0").maxLength(3).build();
FTextField expansionField = new FTextField.Builder().text("0").maxLength(3).build();
FTextField otherField = new FTextField.Builder().text("0").maxLength(3).build();
FTextField customField = new FTextField.Builder().text("0").maxLength(3).build();
JPanel optionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2")); JPanel optionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2"));
optionsPanel.setVisible(false);
optionsPanel.setOpaque(false); optionsPanel.setOpaque(false);
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, growx");
optionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectRandomSets")).fontSize(17).fontStyle(Font.BOLD).build(), "h 40!, span 2");
JPanel leftOptionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2")); // === 2. FORMAT OPTIONS PANEL ===
leftOptionsPanel.setOpaque(false); // This will include a button for each format and a NO-Format Radio Button (default)
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectNumber") + ":").fontSize(14).fontStyle(Font.BOLD).build(), " span 2"); JPanel formatOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center"));
formatOptionsPanel.setOpaque(false);
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblCore") + ":").build()); formatOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") + ":")
leftOptionsPanel.add(coreField, "w 40!"); .fontSize(14).fontStyle(Font.BOLD).build(), "span 1");
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblExpansion") + ":").build());
leftOptionsPanel.add(expansionField, "w 40!");
leftOptionsPanel.add(new FLabel.Builder().text("Other:").build());
leftOptionsPanel.add(otherField, "w 40!");
if (customSetsExist){
leftOptionsPanel.add(new FLabel.Builder().text("Custom Editions:").build());
leftOptionsPanel.add(customField, "w 40!");
}
JPanel rightOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center, wrap 2"));
rightOptionsPanel.setOpaque(false);
rightOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") +":").fontSize(14).fontStyle(Font.BOLD).build(), "span 2");
ButtonGroup formatButtonGroup = new ButtonGroup(); ButtonGroup formatButtonGroup = new ButtonGroup();
List<GameFormat> gameFormats = new ArrayList<>(); List<GameFormat> gameFormats = new ArrayList<>();
FModel.getFormats().getSanctionedList().forEach(gameFormats::add); FModel.getFormats().getSanctionedList().forEach(gameFormats::add);
Map<String, FRadioButton> formatButtonGroupMap = new HashMap<>();
gameFormats.forEach(item -> { gameFormats.forEach(item -> {
if (item.getName().equals("Legacy")) {
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblLegacyOrVintage"));
button.setActionCommand(item.getName());
formatButtonGroup.add(button);
rightOptionsPanel.add(button);
} else if (!item.getName().equals("Vintage")) {
FRadioButton button = new FRadioButton(item.getName()); FRadioButton button = new FRadioButton(item.getName());
button.setActionCommand(item.getName()); button.setActionCommand(item.getName());
formatButtonGroup.add(button); button.addActionListener(new ActionListener() {
rightOptionsPanel.add(button); @Override
public void actionPerformed(ActionEvent e) {
/* Whenever a Format button will be pressed, the status of the UI will be
updated accordingly.
In particular, for each format, the number of allowed editions will be retrieved.
(EMPTY LIST in case of NO RESTRICTIONS).
*/
List<String> allowedSetCodes = item.getAllowedSetCodes();
/* A. NO RESTRICTIONS:
-------------------
All the components will be enabled, namely:
- all nodes in the checkbox tree;
- all spinners are enabled and their maximum value updated accordingly from the Tree status
*/
if (allowedSetCodes.size() == 0) {
for (CardEdition ce : allCardEditions) {
String code = ce.getCode();
FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
checkBoxTree.setNodeEnabledStatus(node, true);
}
for (CardEdition.Type editionType : allEditionTypes.keySet()) {
int numberOfEnabledEditions = allEditionTypes.get(editionType);
if (numberOfEnabledEditions == 0)
// This component will remain disabled, no matter the format selected
continue;
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
FLabel label = labelsEditionTypeMap.get(editionType);
spinner.setEnabled(true);
label.setEnabled(true);
FTreeNode node = checkBoxTree.getNodeByKey(editionType);
if (node != null){
int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
int currentValue = (int) spinner.getValue();
spinner.setValue(Math.min(currentValue, maxValue));
SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
m.setMaximum(maxValue);
} else {
spinner.setValue(0);
}
}
return;
}
/* B. FORMAT RESTRICTIONS:
-----------------------
All components matching with **allowed** editions will be ENABLED.
This includes:
- nodes in the checkbox tree;
- spinners (along with their corresponding MAX values as returned from Tree status).
All components matching with the **BLACK LIST** of editions will be DISABLED
(Same as in the previous case).
*/
List<String> codesToDisable = new ArrayList<>();
Set<CardEdition.Type> typesToDisable = new HashSet<>();
Set<CardEdition.Type> allowedTypes = new HashSet<>();
for (CardEdition ce : allCardEditions) {
String code = ce.getCode();
if (unselectableSets != null && unselectableSets.contains(code))
continue;
if (!allowedSetCodes.contains(code)) {
codesToDisable.add(code);
typesToDisable.add(ce.getType());
} else {
allowedTypes.add(ce.getType());
}
}
// NOTE: We need to distinguish CardEdition.Type not having any actual CardEdition
// in the allowed sets (i.e. to be completely disabled) from those still
// having partial sets to be allowed.
// The latter will result in adjusted maxValues of the corresponding spinner,
// as well as their current value, when necessary.
typesToDisable.removeAll(allowedTypes);
// == Update Checkbox Tree ==
for (String code : codesToDisable) {
FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
checkBoxTree.setNodeEnabledStatus(node, false);
}
for (String code : allowedSetCodes) {
FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
checkBoxTree.setNodeEnabledStatus(node, true);
}
// == update spinners ==
for (CardEdition.Type editionType : typesToDisable) {
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
FLabel label = labelsEditionTypeMap.get(editionType);
spinner.setEnabled(false);
spinner.setValue(0);
label.setEnabled(false);
}
for (CardEdition.Type editionType : allowedTypes) {
if (allEditionTypes.get(editionType) == 0)
continue;
FLabel label = labelsEditionTypeMap.get(editionType);
label.setEnabled(true);
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
spinner.setEnabled(true);
FTreeNode node = checkBoxTree.getNodeByKey(editionType);
if (node != null){
int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
int currentValue = (int) spinner.getValue();
spinner.setValue(Math.min(currentValue, maxValue));
SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
m.setMaximum(maxValue);
} else {
spinner.setValue(0);
}
}
} }
}); });
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblModernCardFrame"));
button.setActionCommand("Modern Card Frame");
formatButtonGroup.add(button); formatButtonGroup.add(button);
rightOptionsPanel.add(button); formatOptionsPanel.add(button);
formatButtonGroupMap.put(item.getName(), button);
});
// NO FORMAT Button
FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction")); FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction"));
noFormatSelectionButton.setActionCommand("No Format Restriction"); noFormatSelectionButton.setActionCommand("No Format");
noFormatSelectionButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (CardEdition ce: allCardEditions){
String code = ce.getCode();
FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
checkBoxTree.setNodeEnabledStatus(node, true);
}
for (CardEdition.Type editionType : allEditionTypes.keySet()) {
if (allEditionTypes.get(editionType) == 0)
// This component will remain disabled, no matter the format selected
continue;
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
FLabel label = labelsEditionTypeMap.get(editionType);
spinner.setEnabled(true);
label.setEnabled(true);
FTreeNode node = checkBoxTree.getNodeByKey(editionType);
if (node != null){
int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
int currentValue = (int) spinner.getValue();
spinner.setValue(Math.min(currentValue, maxValue));
SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
m.setMaximum(maxValue);
} else {
spinner.setValue(0);
}
}
}
});
formatButtonGroup.add(noFormatSelectionButton); formatButtonGroup.add(noFormatSelectionButton);
rightOptionsPanel.add(noFormatSelectionButton); formatOptionsPanel.add(noFormatSelectionButton);
formatButtonGroupMap.put("No Format", noFormatSelectionButton);
noFormatSelectionButton.setSelected(true); noFormatSelectionButton.setSelected(true);
optionsPanel.add(leftOptionsPanel, "w 33%:40%:78%"); // === Update Option Panel ===
optionsPanel.add(rightOptionsPanel, "w 33%:60%:78%"); optionsPanel.add(formatOptionsPanel, "span 2, w 100%");
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2");
FButton randomSelectionButton = new FButton(Localizer.getInstance().getMessage("lblRandomizeSets")); // === EDITION (PER TYPE) SELECTION PANEL ===
randomSelectionButton.addActionListener(actionEvent -> { // Selected Editions Panel
JPanel editionSelectionPanel = new JPanel(new MigLayout("insets 10, gap 25 5, wrap 1, align left"));
editionSelectionPanel.setOpaque(false);
editionSelectionPanel.add(new FLabel.Builder().text(
Localizer.getInstance().getMessage("lblCardEditionTypeList")).fontSize(14)
.fontStyle(Font.BOLD).build(), "h 40!, w 100%, center, span 1");
this.checkBoxTree.setOpaque(false);
FScrollPane selectionScroller = new FScrollPane(checkBoxTree, true);
editionSelectionPanel.add(selectionScroller, "span 1, w 100%");
int numberOfCoreSets = Integer.parseInt(coreField.getText()); // ======== ADD ACTION LISTENERS TO CLEAR AND RANDOM SELECT BUTTONS
int numberOfExpansionSets = Integer.parseInt(expansionField.getText());
int numberOfOtherSets = Integer.parseInt(otherField.getText());
int numberOfCustomeSets = 0;
if (customSetsExist)
numberOfCustomeSets = Integer.parseInt(customField.getText());
for (FCheckBox coreSet : coreSets) {
coreSet.setSelected(false);
}
for (FCheckBox expansionSet : expansionSets) {
expansionSet.setSelected(false);
}
for (FCheckBox otherSet : otherSets) {
otherSet.setSelected(false);
}
if (customSetsExist){
for (FCheckBox customSet : customSets) {
customSet.setSelected(false);
}
}
Predicate<CardEdition> formatPredicate = null;
for (GameFormat gameFormat : gameFormats) {
if (gameFormat.getName().equals(formatButtonGroup.getSelection().getActionCommand())) {
formatPredicate = edition -> gameFormat.editionLegalPredicate.apply(edition) && (unselectableSets == null || !unselectableSets.contains(edition.getCode()));
} else if (formatButtonGroup.getSelection().getActionCommand().equals("Modern Card Frame")) {
formatPredicate = edition -> edition.getDate().after(new Date(1059350399L * 1000L)) && (unselectableSets == null || !unselectableSets.contains(edition.getCode()));
} else if (formatButtonGroup.getSelection().getActionCommand().equals("No Format Restriction")) {
formatPredicate = edition -> unselectableSets == null || !unselectableSets.contains(edition.getCode());
}
}
List<CardEdition> filteredCoreSets = new ArrayList<>();
for (CardEdition edition : editions) {
if (edition.getType() == CardEdition.Type.CORE) {
if (formatPredicate != null && formatPredicate.test(edition)) {
filteredCoreSets.add(edition);
}
}
}
List<CardEdition> filteredExpansionSets = new ArrayList<>();
for (CardEdition edition : editions) {
if (edition.getType() == CardEdition.Type.EXPANSION) {
if (formatPredicate != null && formatPredicate.test(edition)) {
filteredExpansionSets.add(edition);
}
}
}
List<CardEdition> filteredOtherSets = new ArrayList<>();
for (CardEdition edition : editions) {
if (edition.getType() != CardEdition.Type.CORE && edition.getType() != CardEdition.Type.EXPANSION) {
if (formatPredicate != null && formatPredicate.test(edition)) {
filteredOtherSets.add(edition);
}
}
}
Collections.shuffle(filteredCoreSets);
Collections.shuffle(filteredExpansionSets);
Collections.shuffle(filteredOtherSets);
List<CardEdition> filteredCustomSets = new ArrayList<>();
if (customSetsExist){
for (CardEdition edition : customEditions) {
if (formatPredicate != null && formatPredicate.test(edition)) {
filteredCustomSets.add(edition);
}
}
Collections.shuffle(filteredCustomSets);
}
for (int i = 0; i < numberOfCoreSets && i < filteredCoreSets.size(); i++) {
String name = TextUtil.concatWithSpace(filteredCoreSets.get(i).getName(), TextUtil.enclosedParen(filteredCoreSets.get(i).getCode()));
for (FCheckBox set : coreSets) {
if (set.getText().equals(name)) {
set.setSelected(true);
}
}
}
for (int i = 0; i < numberOfExpansionSets && i < filteredExpansionSets.size(); i++) {
String name = TextUtil.concatWithSpace(filteredExpansionSets.get(i).getName(), TextUtil.enclosedParen(filteredExpansionSets.get(i).getCode()));
for (FCheckBox set : expansionSets) {
if (set.getText().equals(name)) {
set.setSelected(true);
}
}
}
for (int i = 0; i < numberOfOtherSets && i < filteredOtherSets.size(); i++) {
String name = TextUtil.concatWithSpace(filteredOtherSets.get(i).getName(), TextUtil.enclosedParen(filteredOtherSets.get(i).getCode()));
for (FCheckBox set : otherSets) {
if (set.getText().equals(name)) {
set.setSelected(true);
}
}
}
if (customSetsExist){
for (int i = 0; i < numberOfCustomeSets && i < filteredCustomSets.size(); i++) {
String name = TextUtil.concatWithSpace(filteredCustomSets.get(i).getName(),
TextUtil.enclosedParen(filteredCustomSets.get(i).getCode()));
for (FCheckBox set : customSets) {
if (set.getText().equals(name)) {
set.setSelected(true);
}
}
}
}
panel.repaintSelf();
});
FButton clearSelectionButton = new FButton(Localizer.getInstance().getMessage("lblClearSelection"));
clearSelectionButton.addActionListener(actionEvent -> { clearSelectionButton.addActionListener(actionEvent -> {
for (FCheckBox coreSet : coreSets) { this.checkBoxTree.resetCheckingState();
coreSet.setSelected(false); allEditionTypes.forEach((editionType, count) -> {
if (count == 0)
return;
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
FLabel label = labelsEditionTypeMap.get(editionType);
spinner.setValue(0);
spinner.setEnabled(true);
label.setEnabled(true);
});
noFormatSelectionButton.setSelected(true);
cbWantReprints.setSelected(false);
mainDialogPanel.repaintSelf();
});
randomSelectionButton.addActionListener(actionEvent -> {
Map<CardEdition.Type, Integer> countPerEditionType = new HashMap<>();
for (CardEdition.Type editionType: allEditionTypes.keySet()){
if (allEditionTypes.get(editionType) == 0)
continue;
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
if (!spinner.isEnabled())
continue;
int value = (int) spinner.getValue();
if (value > 0)
countPerEditionType.put(editionType, value);
} }
for (FCheckBox expansionSet : expansionSets) { // We can safely reset selections as this button would not be enabled at all
expansionSet.setSelected(false); // if at least one spinner has been modified, and so countPerEdition updated.
} checkBoxTree.resetCheckingState();
for (FCheckBox otherSet : otherSets) { String selectedFormat = formatButtonGroup.getSelection().getActionCommand();
otherSet.setSelected(false); FRadioButton formatButton = formatButtonGroupMap.get(selectedFormat);
} formatButton.doClick();
if (customSetsExist){ for (CardEdition.Type editionType : countPerEditionType.keySet()){
for (FCheckBox cmSet : customSets) { int totalToSelect = countPerEditionType.get(editionType);
cmSet.setSelected(false); FTreeNode setTypeNode = checkBoxTree.getNodeByKey(editionType);
if (setTypeNode != null){
List<FTreeNode> activeChildNodes = checkBoxTree.getActiveChildNodes(setTypeNode);
Collections.shuffle(activeChildNodes);
for (int i = 0; i < totalToSelect; i++)
checkBoxTree.setNodeCheckStatus(activeChildNodes.get(i), true);
} }
} }
panel.repaintSelf(); mainDialogPanel.repaintSelf();
}); });
FButton showOptionsButton = new FButton(Localizer.getInstance().getMessage("lblShowOptions")); // ===================================================================
showOptionsButton.addActionListener(actionEvent -> {
optionsPanel.setVisible(true);
showOptionsButton.setVisible(false);
});
FButton hideOptionsButton = new FButton(Localizer.getInstance().getMessage("lblHideOptions")); mainDialogPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets"))
hideOptionsButton.addActionListener(actionEvent -> { .fontSize(20).build(), "center, span, wrap, gaptop 10");
optionsPanel.setVisible(false); mainDialogPanel.add(editionSelectionPanel, "aligny top, w 50%, span 1");
showOptionsButton.setVisible(true); mainDialogPanel.add(randomSelectionPanel, "aligny top, w 50%, span 1");
}); mainDialogPanel.add(optionsPanel, "center, w 100, span 2");
JPanel buttonPanel = new JPanel(new MigLayout("h 50!, center, gap 10, insets 0, ay center"));
buttonPanel.setOpaque(false);
buttonPanel.add(randomSelectionButton, "w 175!, h 28!");
buttonPanel.add(clearSelectionButton, "w 175!, h 28!");
buttonPanel.add(hideOptionsButton, " w 175!, h 28!");
optionsPanel.add(buttonPanel, "span 2, growx");
if (showWantReprintsCheckbox) {
optionsPanel.add(cbWantReprints, "center, span, wrap");
}
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, growx");
panel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets")).fontSize(20).build(), "center, span, wrap, gaptop 10");
String constraints = "aligny top";
panel.add(makeCheckBoxList(coreSets,
Localizer.getInstance().getMessage("lblCoreSets"), true),
constraints);
panel.add(makeCheckBoxList(expansionSets,
Localizer.getInstance().getMessage("lblExpansions"), false),
constraints);
panel.add(makeCheckBoxList(otherSets,
Localizer.getInstance().getMessage("lblOtherSets"), false),
constraints);
if (customSetsExist){
panel.add(makeCheckBoxList(customSets,
Localizer.getInstance().getMessage("lblCustomSets"), false),
constraints);
}
panel.add(showOptionsButton, "center, w 230!, h 30!, gap 10 0 20 0, span 3, hidemode 3");
panel.add(optionsPanel, "center, w 100, span 3, growx, hidemode 3");
final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel(); final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center")); overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center"));
@@ -378,17 +469,46 @@ public class DialogChooseSets {
JPanel southPanel = new JPanel(new MigLayout("insets 10, gap 30, ax center")); JPanel southPanel = new JPanel(new MigLayout("insets 10, gap 30, ax center"));
southPanel.setOpaque(false); southPanel.setOpaque(false);
southPanel.add(btnOk, "center, w 200!, h 30!"); southPanel.add(btnOk, "center, w 250!, h 30!");
southPanel.add(btnCancel, "center, w 200!, h 30!"); southPanel.add(btnCancel, "center, w 250!, h 30!");
panel.add(southPanel, "dock south, gapBottom 10"); mainDialogPanel.add(southPanel, "dock south, gapBottom 10");
overlay.add(panel); overlay.add(mainDialogPanel);
panel.getRootPane().setDefaultButton(btnOk); mainDialogPanel.getRootPane().setDefaultButton(btnOk);
SOverlayUtils.showOverlay(); SOverlayUtils.showOverlay();
} }
private int getMainDialogWidth() {
int winWidth = Singletons.getView().getFrame().getSize().width;
System.out.println("Win Width " + winWidth);
int[] sizeBoundaries = new int[] {800, 1024, 1280, 2048};
return calculateRelativePanelDimension(winWidth, 90, sizeBoundaries);
}
// So far, not yet used, but left here just in case
private int getMainDialogHeight() {
int winHeight = Singletons.getView().getFrame().getSize().height;
System.out.println("Win Height " + winHeight);
int[] sizeBoundaries = new int[] {600, 720, 780, 1024};
return calculateRelativePanelDimension(winHeight, 40, sizeBoundaries);
}
private int calculateRelativePanelDimension(int winDim, int ratio, int[] sizeBoundaries){
int relativeWinDimension = winDim * ratio / 100;
if (winDim < sizeBoundaries[0])
return relativeWinDimension;
for (int i = 1; i < sizeBoundaries.length; i++){
int left = sizeBoundaries[i-1];
int right = sizeBoundaries[i];
if (winDim <= left || winDim > right)
continue;
return Math.min(right*90/100, relativeWinDimension);
}
return sizeBoundaries[sizeBoundaries.length - 1] * 90 / 100; // Max Size fixed
}
public void setOkCallback(Runnable onOk) { public void setOkCallback(Runnable onOk) {
okCallback = onOk; okCallback = onOk;
} }
@@ -401,46 +521,16 @@ public class DialogChooseSets {
return wantReprints; return wantReprints;
} }
public void setWantReprintsCB(boolean isSet) {
cbWantReprints.setSelected(isSet);
}
private JPanel makeCheckBoxList(List<FCheckBox> sets, String title, boolean focused) {
choices.addAll(sets);
final FCheckBoxList<FCheckBox> cbl = new FCheckBoxList<>(false);
cbl.setListData(sets.toArray(new FCheckBox[sets.size()]));
cbl.setVisibleRowCount(20);
if (focused) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
cbl.requestFocusInWindow();
}
});
}
JPanel pnl = new JPanel(new MigLayout("center, wrap"));
pnl.setOpaque(false);
pnl.add(new FLabel.Builder().text(title).build());
pnl.add(new FScrollPane(cbl, true));
return pnl;
}
private void handleOk() { private void handleOk() {
Object[] checkedValues = this.checkBoxTree.getCheckedValues(true);
for (FCheckBox box : choices) { for (Object data: checkedValues){
if (box.isSelected()) { CardEdition edition = (CardEdition) data;
selectedSets.add(box.getName()); selectedSets.add(edition.getCode());
} }
wantReprints = cbWantReprints.isSelected(); wantReprints = cbWantReprints.isSelected();
}
if (null != okCallback) { if (null != okCallback) {
okCallback.run(); okCallback.run();
} }
} }
} }

View File

@@ -0,0 +1,646 @@
package forge.toolbox;
import net.miginfocom.swing.MigLayout;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.IconUIResource;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.*;
/**
* A custom JTree of FCheckBox items using Forge skin properties that allows Path selection.
* The implementation details:
* ===========================
* 1) Custom `TreeCellRenderer` that renders a Tree node as a FCheckbox.
* 1.1) When selected, the checkbox selection is changed instead of the label background and border.
* 2) Replaced the `Selection Model` by a `DefaultTreeSelectionModel` overridden inline, that has empty implementation
* to override default selection mechanism.
*
* 3) New (custom) event type for checking of the checkboxes, i.e. `CheckChangeEvent`
* 4) Inner HashSet of paths (i.e. TreePath) that helps retrieving the state of each node.
* 4.1) A custom Data Object (i.e. `TreeNodeState`) is defined to embed the current state of each tree node
*
* <p>
* based on code at
* https://stackoverflow.com/questions/21847411/java-swing-need-a-good-quality-developed-jtree-with-checkboxes
*/
public class FCheckBoxTree extends JTree {
// === FTreeNodeData ===
/** Custom Data Class for each node in the Tree.
* The Data class is pretty straightforward, and embeds three main properties:
* --> label: that will be used by the TreeCellRenderer to label the checkbox rendering the node
* --> value: the actual value stored in the NodeInfo to be collected and used afterwards. This must
* be any Comparable object.
* --> key: a unique value identifying this node data entry (if not provided, the hashcode of value will
* be used by default). This property is mirrored by the FTreeNode instance encapsulating the data
* class to uniquely reference a node into the tree. Therefore, this key value should be
* passed in accordingly, with this in mind!
*/
public static class FTreeNodeData implements Comparable<FTreeNodeData> {
public Object key;
public String label;
public Comparable item;
public boolean isEnabled = true;
public boolean isSelected = false;
public FTreeNodeData(Comparable value) {
this(value, value.toString(), value.hashCode());
}
public FTreeNodeData(Comparable value, String label) {
this(value, label, value.hashCode());
}
public FTreeNodeData(Comparable value, String name, Object key) {
this.item = value;
this.label = name;
this.key = key;
}
public Object getKey(){ return this.key; }
@Override
public int hashCode(){
return this.item.hashCode();
}
@Override
public String toString() { return "FTreeNodeInfo["+this.label+", "+this.item.toString()+"]";}
@Override
public int compareTo(FTreeNodeData o) {
return this.item.compareTo(o.item);
}
}
// === FTreeNode ===
/**
* Custom TreeNode instance used as a proxy to handle recursive data structures
* The custom class defines a bunch of helpers overloaded methods to ensure
* data encapsulation and data types in custom JTree Model.
*/
public static class FTreeNode extends DefaultMutableTreeNode {
public FTreeNode(FTreeNodeData object){
super(object, true);
}
// Helper Method to quickly add child nodes from a list of FTreeNodeInfo instances
public void add(List<FTreeNodeData> nodesData){
for (FTreeNodeData dataObject : nodesData)
this.add(new FTreeNode(dataObject));
}
public void add(FTreeNodeData dataObject){
this.add(new FTreeNode(dataObject));
}
public FTreeNodeData getUserObject(){
return (FTreeNodeData) super.getUserObject();
}
@Override
public String toString() { return "FTreeNode["+this.getUserObject().toString()+"]";}
public Object getKey() { return this.getUserObject().getKey(); }
}
/**
* Defining data structure that will enable to fast check-indicate the state of each node.
* This class is central to properly handle the interaction with the component, and so
* to recursively traverse the tree and update||query the status of each nested component.
*/
private static class TreeNodeState {
boolean isSelected;
boolean isEnabled;
int numberOfChildren;
int selectedChildrenCount;
int enabledChildrenCount;
public TreeNodeState(boolean isSelected, boolean isEnabled, int numberOfChildren,
int selectedChildrenCount, int enabledChildrenCount) {
this.isSelected = isSelected && isEnabled;
this.isEnabled = isEnabled;
this.numberOfChildren = numberOfChildren;
this.selectedChildrenCount = selectedChildrenCount;
this.enabledChildrenCount = enabledChildrenCount;
}
public boolean hasChildren() { return this.numberOfChildren > 0;}
public boolean allChildrenSelected(){ return this.numberOfChildren == this.selectedChildrenCount; };
public boolean allChildrenEnabled(){ return this.enabledChildrenCount == this.numberOfChildren; };
}
// == Fields of the FCheckboxTree class ==
// =======================================
FCheckBoxTree selfPointer = this;
private static final String ROOTNODE_LABEL = "/"; // won't be displayed
private Map<Object, FTreeNode> nodesSet = new HashMap<>(); // A map of all nodes in the tree (model)
private HashMap<TreePath, TreeNodeState> treeNodesStates;
private HashSet<TreePath> checkedPaths = new HashSet<>();
private TreePath lastSelectedPath; // the last path user interacted with (either checked, or unchecked)
private TreePath lastCheckedPath; // last path checked
// == CONSTRUCTOR METHOD ==
// ========================
public FCheckBoxTree(){
super(new FTreeNode(new FTreeNodeData(ROOTNODE_LABEL)));
// Disabling toggling by double-click
this.setToggleClickCount(0);
// Replacing default TreeUI class to customise the Icons and Look and feel
this.setUI(new FCheckBoxTreeUI());
// Replacing default Cell Renderer
FCheckBoxTreeCellRenderer cellRenderer = new FCheckBoxTreeCellRenderer();
this.setCellRenderer(cellRenderer);
// Replacing default selection model with an empty one
DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {
// Totally disabling the selection mechanism
public void setSelectionPath(TreePath path) {}
public void addSelectionPath(TreePath path) {}
public void removeSelectionPath(TreePath path) {}
public void setSelectionPaths(TreePath[] pPaths) {}
};
// Enabling Path Auto-selection Mechanism and Tree-Check on MouseClick
this.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
TreePath tp = selfPointer.getPathForLocation(e.getX(), e.getY());
if (tp == null) {
return;
}
boolean enabledStatus = treeNodesStates.get(tp).isEnabled;
// NOTE: this is PARAMOUNT IMPORTANT!
// Checkbox selection will be inhibited when nodes are disabled!
if (!enabledStatus)
return;
boolean checkStatus = !treeNodesStates.get(tp).isSelected;
setPathCheckStatus(tp, checkStatus);
}
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
});
this.setSelectionModel(dtsm);
this.setRootVisible(false);
this.setShowsRootHandles(true);
}
// == PUBLIC API METHODS ==
// ========================
public TreePath getLastSelectedPath(){ return this.lastSelectedPath; }
public TreePath getLastCheckedBox(){ return this.lastCheckedPath; }
@Override
public void setModel(TreeModel newModel) {
super.setModel(newModel);
initModelCheckState();
}
public TreePath[] getCheckedPaths() {
return checkedPaths.toArray(new TreePath[checkedPaths.size()]);
}
/**
* Returns all the values stored in each checked node.
* Note: Values in FTreeNodeData are save as objects, therefore these array should
* be casted afterwards, accordingly.
* */
public Object[] getCheckedValues(boolean leafNodesOnly) {
ArrayList<Object> checkedValues = new ArrayList<>();
for (TreePath tp : this.getCheckedPaths()){
FTreeNode node = (FTreeNode) tp.getLastPathComponent();
boolean getValueFromNode = (!leafNodesOnly) || node.isLeaf();
if (getValueFromNode) {
FTreeNodeData data = node.getUserObject();
checkedValues.add(data.item);
}
}
return checkedValues.toArray(new Object[checkedValues.size()]);
}
// Returns true in case that the node is selected, has children but not all of them are selected
public boolean isSelectedPartially(TreePath path) {
TreeNodeState cn = treeNodesStates.get(path);
return cn.isEnabled && cn.isSelected && cn.hasChildren() && !cn.allChildrenSelected();
}
public void resetCheckingState() {
treeNodesStates = new HashMap<>();
checkedPaths = new HashSet<>();
nodesSet = new HashMap<>();
FTreeNode node = (FTreeNode) getModel().getRoot();
if (node == null)
return;
addSubtreeToCheckingStateTracking(node, true);
}
public FTreeNode getNodeByKey(Object key){
FTreeNode node = nodesSet.getOrDefault(key, null);
if (node != null)
return node;
return nodesSet.getOrDefault(key.hashCode(), null);
}
public int getNumberOfActiveChildNodes(FTreeNode node){
TreeNodeState cn = getTreeNodeState(node);
if (cn != null)
return cn.enabledChildrenCount;
return -1;
}
public List<FTreeNode> getActiveChildNodes(FTreeNode parent){
List<FTreeNode> activeChildren = new ArrayList<>();
for (int i = 0; i < parent.getChildCount(); i++) {
FTreeNode childNode = (FTreeNode) parent.getChildAt(i);
TreeNodeState cn = getTreeNodeState(childNode);
if ((cn != null) && (cn.isEnabled))
activeChildren.add(childNode);
}
return activeChildren;
}
public int getNumberOfSelectedChildNodes(FTreeNode node){
TreeNodeState cn = getTreeNodeState(node);
if (cn != null)
return cn.selectedChildrenCount;
return -1;
}
public void setNodeCheckStatus(FTreeNode node, boolean isChecked){
TreeNode[] path = node.getPath();
TreePath treePath = new TreePath(path);
setPathCheckStatus(treePath, isChecked);
}
public void setNodeEnabledStatus(FTreeNode node, boolean isEnabled){
TreeNode[] path = node.getPath();
TreePath treePath = new TreePath(path);
setPathEnableStatus(treePath, isEnabled);
}
/** Initialise a new TreeModel from the input Subtree represented as a Map of FTreeNodeInfo instances.
* In particular, each key will be interpreted as sibling nodes, and directly attached to the main ROOT
* (not displayed), whilst each FTreeNodeInfo in the corresponding lists will be treated as as child leaf nodes.
*
*/
public void setTreeData(TreeMap<FTreeNodeData, List<FTreeNodeData>> nodesMap) {
FTreeNode rootNode = new FTreeNode(new FTreeNodeData(FCheckBoxTree.ROOTNODE_LABEL));
for (FTreeNodeData keyNodeInfo : nodesMap.keySet()) {
FTreeNode keyNode = new FTreeNode(keyNodeInfo);
rootNode.add(keyNode);
for (FTreeNodeData childNodeInfo : nodesMap.get(keyNodeInfo))
keyNode.add(childNodeInfo);
}
DefaultTreeModel defaultTreeModel = new DefaultTreeModel(rootNode);
this.setModel(defaultTreeModel);
}
// Set an option in the cell rendered to enable or disable the visualisation of child nodes count
public void showNodesCount(){
this.setCellRenderer(new FCheckBoxTreeCellRenderer(true));
}
public void hideNodesCount(){
this.setCellRenderer(new FCheckBoxTreeCellRenderer(false));
}
// == PRIVATE API METHODS ==
// ========================
private void initModelCheckState(){
treeNodesStates = new HashMap<>();
checkedPaths = new HashSet<>();
nodesSet = new HashMap<>();
FTreeNode node = (FTreeNode) getModel().getRoot();
if (node == null || node.getChildCount() == 0)
return;
addSubtreeToCheckingStateTracking(node, false);
}
private void addSubtreeToCheckingStateTracking(FTreeNode node, boolean resetSelectState) {
FTreeNode prevNode = nodesSet.put(node.getKey(), node);
if (prevNode != null)
throw new RuntimeException("Node " + node + "already present in Nodes Set (key:"+node.getKey()+")");
TreeNode[] path = node.getPath();
FTreeNodeData nodeData = node.getUserObject();
boolean selectStatus = !resetSelectState && nodeData.isSelected;
TreePath treePath = new TreePath(path);
TreeNodeState nodeState = new TreeNodeState(selectStatus, nodeData.isEnabled, node.getChildCount(),
0, node.getChildCount());
treeNodesStates.put(treePath, nodeState);
TreePath lastChildNodePath = null;
for (int i = 0; i < node.getChildCount(); i++) {
lastChildNodePath = treePath.pathByAddingChild(node.getChildAt(i));
addSubtreeToCheckingStateTracking((FTreeNode) lastChildNodePath.getLastPathComponent(), resetSelectState);
}
if (lastChildNodePath != null)
updatePredecessors(lastChildNodePath);
}
private void setPathCheckStatus(TreePath tp, boolean checkStatus) {
setCheckedStatusOnTree(tp, checkStatus);
updatePredecessors(tp);
// Firing the check change event
fireCheckChangeEvent(new TreeCheckChangeEvent(new Object()));
// Repainting tree after the data structures were updated
selfPointer.repaint();
}
private void setPathEnableStatus(TreePath tp, boolean enableStatus) {
percolateEnabledStatusOnSubtree(tp, enableStatus);
updatePredecessors(tp);
// Firing the enabled change event
fireEnabledChangeEvent(new TreeEnabledChangeEvent(new Object()));
// Repainting tree after the data structures were updated
selfPointer.repaint();
}
private TreeNodeState getTreeNodeState(FTreeNode node) {
TreeNode[] path = node.getPath();
TreePath treePath = new TreePath(path);
return this.treeNodesStates.get(treePath);
}
protected boolean isRoot(TreePath tp){
return (tp.getParentPath() == null);
}
// Whenever a node state changes, updates the inner state of ancestors accordingly
protected void updatePredecessors(TreePath tp) {
if (isRoot(tp))
return; // STOP recursion
TreePath parentPath = tp.getParentPath();
TreeNodeState parentTreeNodeState = treeNodesStates.get(parentPath);
FTreeNode parentTreeNode = (FTreeNode) parentPath.getLastPathComponent();
parentTreeNodeState.selectedChildrenCount = 0;
parentTreeNodeState.enabledChildrenCount = 0;
parentTreeNodeState.isSelected = false;
parentTreeNodeState.isEnabled = true;
for (int i = 0; i < parentTreeNode.getChildCount(); i++) {
TreePath childPath = parentPath.pathByAddingChild(parentTreeNode.getChildAt(i));
TreeNodeState childTreeNodeState = treeNodesStates.get(childPath);
if (childTreeNodeState == null)
continue;
if (childTreeNodeState.isEnabled) {
parentTreeNodeState.enabledChildrenCount += 1;
if (childTreeNodeState.isSelected) {
parentTreeNodeState.selectedChildrenCount += 1;
parentTreeNodeState.isSelected = true;
}
}
}
if (parentTreeNodeState.enabledChildrenCount == 0)
parentTreeNodeState.isEnabled = false;
if (parentTreeNodeState.isSelected)
checkedPaths.add(parentPath);
else
checkedPaths.remove(parentPath);
// Go Up onto the ancestors hierarchy
updatePredecessors(parentPath);
}
// This method is the one that should be used whenever a new state change event happens
protected void setCheckedStatusOnTree(TreePath tp, boolean isChecked){
this.lastSelectedPath = tp;
if (isChecked)
this.lastCheckedPath = tp;
percolateCheckedStatusOnSubTree(tp, isChecked);
}
// Recursively checks/unchecks a subtree using DFS on the subtree induced by current node
private void percolateCheckedStatusOnSubTree(TreePath tp, boolean isChecked) {
TreeNodeState cn = treeNodesStates.get(tp);
cn.isSelected = cn.isEnabled && isChecked;
FTreeNode node = (FTreeNode) tp.getLastPathComponent();
for (int i = 0; i < node.getChildCount(); i++)
percolateCheckedStatusOnSubTree(tp.pathByAddingChild(node.getChildAt(i)), isChecked);
cn.selectedChildrenCount = isChecked ? cn.enabledChildrenCount : 0;
if (cn.isEnabled) {
if (isChecked)
checkedPaths.add(tp);
else
checkedPaths.remove(tp);
}
}
private void percolateEnabledStatusOnSubtree(TreePath tp, boolean isEnabled){
TreeNodeState cn = treeNodesStates.get(tp);
cn.isEnabled = isEnabled;
cn.isSelected = isEnabled && cn.isSelected;
if (!cn.isSelected) {
cn.selectedChildrenCount = 0; // selection applies to all nodes in subtree, so we can safely set this.
checkedPaths.remove(tp);
}
FTreeNode node = (FTreeNode) tp.getLastPathComponent();
for (int i = 0; i < node.getChildCount(); i++)
percolateEnabledStatusOnSubtree(tp.pathByAddingChild(node.getChildAt(i)), isEnabled);
}
// === CUSTOM CELL RENDERED ===
// ============================
// NOTE: This class ignores the original "selection" mechanism and determines the status
// of a single (FCheckBox) node based on the "checked" property.
private class FCheckBoxTreeCellRenderer extends JPanel implements TreeCellRenderer {
FCheckBox checkBox;
private final FSkin.SkinFont CHECKBOX_LABEL_FONT = FSkin.getFont(14);
private final FSkin.SkinColor CHECKBOX_LABEL_COLOUR = FSkin.getColor(FSkin.Colors.CLR_TEXT);
private final Color CHECKBOX_SELECTED_LABEL_COLOUR = new Color(252, 226, 137);
private final boolean displayNodesCount;
public FCheckBoxTreeCellRenderer(boolean displayNodesCount) {
super();
this.setLayout(new MigLayout("insets 0, gap 0"));
this.setBorder(null);
this.setOpaque(false);
this.checkBox = new FCheckBox();
this.displayNodesCount = displayNodesCount;
add(this.checkBox, "left, gaptop 2, w 250::450, h 20!");
}
public FCheckBoxTreeCellRenderer() {
this(true);
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
FTreeNode node = (FTreeNode) value;
FTreeNodeData nodeInfo = node.getUserObject();
TreePath tp = new TreePath(node.getPath());
TreeNodeState cn = treeNodesStates.get(tp);
if (cn == null)
return this;
this.checkBox.setEnabled(cn.isEnabled);
this.checkBox.setSelected(cn.isSelected);
String chkBoxTxt = nodeInfo.label;
int disabledNodes = cn.numberOfChildren - cn.enabledChildrenCount;
int totalActiveNodes = cn.numberOfChildren - disabledNodes;
if (this.displayNodesCount && !node.isLeaf() && cn.numberOfChildren > 0 && totalActiveNodes > 0) {
chkBoxTxt += String.format(" (%d/%d)", cn.selectedChildrenCount, totalActiveNodes);;
}
this.checkBox.setText(chkBoxTxt);
this.checkBox.setName(nodeInfo.item.toString());
if (cn.isSelected) {
this.checkBox.setForeground(CHECKBOX_SELECTED_LABEL_COLOUR);
} else {
this.checkBox.setForeground(CHECKBOX_LABEL_COLOUR);
}
this.checkBox.setFont(CHECKBOX_LABEL_FONT);
return this;
}
}
// === CUSTOM TREE UI ==
// =====================
// Note: This class rewrites icons for collapsed and expanded nodes with the same polygon
// glyph used in ImageView. Also, no lines are drawn (neither vertical or horizontal)
private static class FCheckBoxTreeUI extends BasicTreeUI {
private final IconUIResource nodeCollapsedIcon;
private final IconUIResource nodeExpandedIcon;
public FCheckBoxTreeUI(){
super();
this.nodeCollapsedIcon = new IconUIResource(new NodeIcon(true));
this.nodeExpandedIcon = new IconUIResource(new NodeIcon(false));
}
@Override
protected void paintHorizontalLine(Graphics g,JComponent c,int y,int left,int right){}
@Override
protected void paintVerticalLine(Graphics g,JComponent c,int x,int top,int bottom){}
@Override
public Icon getCollapsedIcon(){ return this.nodeCollapsedIcon;}
@Override
public Icon getExpandedIcon(){ return this.nodeExpandedIcon; }
static class NodeIcon implements Icon {
private static final int SIZE = 9;
private static final int HEADER_HEIGHT = 6;
private static final int HEADER_GLYPH_WIDTH = 8;
private final boolean isCollapsed;
public NodeIcon(boolean isCollapsed) {
this.isCollapsed = isCollapsed;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
final Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
FSkin.setGraphicsFont(g2d, FSkin.getFont());
Polygon glyph = new Polygon();
int offset = HEADER_GLYPH_WIDTH / 2 + 1;
x += 4;
y += HEADER_HEIGHT / 2;
if (!this.isCollapsed) {
glyph.addPoint(x - offset + 2, y + offset - 1);
glyph.addPoint(x + offset, y + offset - 1);
glyph.addPoint(x + offset, y - offset + 1);
} else {
y++;
glyph.addPoint(x, y - offset);
glyph.addPoint(x + offset, y);
glyph.addPoint(x, y + offset);
}
g2d.fill(glyph);
}
@Override public int getIconWidth() {
return SIZE;
}
@Override public int getIconHeight() {
return SIZE;
}
}
}
// === CUSTOM EVENT TYPE AND EVENT HANDLER ===
// ===========================================
// NEW EVENT TYPE
protected EventListenerList listenerList = new EventListenerList();
public static class TreeCheckChangeEvent extends EventObject {
public TreeCheckChangeEvent(Object source) { super(source); }
}
// NEW Custom Event Listener for the new `CheckChangeEvent`, which is fired every time a check state of a
// checkbox changes.
interface CheckChangeEventListener extends EventListener {
public void checkStateChanged(TreeCheckChangeEvent event);
}
public void addCheckChangeEventListener(CheckChangeEventListener listener) {
if (listenerList == null)
return;
listenerList.add(CheckChangeEventListener.class, listener);
}
public void removeCheckChangeEventListener(CheckChangeEventListener listener) {
if (listenerList == null)
return;
listenerList.remove(CheckChangeEventListener.class, listener);
}
void fireCheckChangeEvent(TreeCheckChangeEvent evt) {
if (listenerList == null)
return;
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == CheckChangeEventListener.class) {
((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
}
}
}
public static class TreeEnabledChangeEvent extends EventObject {
public TreeEnabledChangeEvent(Object source) { super(source); }
}
// NEW Custom Event Listener for the new `CheckChangeEvent`, which is fired every time a check state of a
// checkbox changes.
interface EnableChangeEventListener extends EventListener {
public void enabledStateChanged(TreeEnabledChangeEvent event);
}
public void addEnableChangeEventListener(EnableChangeEventListener listener) {
if (listenerList == null)
return;
listenerList.add(EnableChangeEventListener.class, listener);
}
public void removeEnableChangeEventListener(EnableChangeEventListener listener) {
if (listenerList == null)
return;
listenerList.remove(EnableChangeEventListener.class, listener);
}
void fireEnabledChangeEvent(TreeEnabledChangeEvent evt) {
if (listenerList == null)
return;
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == CheckChangeEventListener.class) {
((EnableChangeEventListener) listeners[i + 1]).enabledStateChanged(evt);
}
}
}
}

View File

@@ -142,7 +142,7 @@ public class FSkin {
* *
* @param clr0 {@link java.awt.Color} * @param clr0 {@link java.awt.Color}
* @param step int * @param step int
* @return {@link java.awt.CFaceolor} * @return {@link java.awt.Color}
*/ */
public static Color stepColor(final Color clr0, final int step) { public static Color stepColor(final Color clr0, final int step) {
int r = clr0.getRed(); int r = clr0.getRed();

View File

@@ -136,16 +136,25 @@ public abstract class FormatFilter<T extends InventoryItem> extends ItemFilter<T
lstSets.addGroup("Core Sets"); lstSets.addGroup("Core Sets");
lstSets.addGroup("Expansions"); lstSets.addGroup("Expansions");
lstSets.addGroup("Duel Decks");
lstSets.addGroup("From the Vault");
lstSets.addGroup("Premium Deck Series");
lstSets.addGroup("Reprint Sets");
lstSets.addGroup("Starter Sets"); lstSets.addGroup("Starter Sets");
lstSets.addGroup("Reprint Sets");
lstSets.addGroup("Boxed Sets");
lstSets.addGroup("Collector's Edition");
lstSets.addGroup("Duel Decks");
lstSets.addGroup("Promo Sets"); lstSets.addGroup("Promo Sets");
lstSets.addGroup("Digital Sets"); lstSets.addGroup("Digital Sets");
lstSets.addGroup("Draft Innovation Sets");
lstSets.addGroup("Commander Sets");
lstSets.addGroup("Multiplayer Sets");
lstSets.addGroup("Other Supplemental Sets");
lstSets.addGroup("Funny Sets"); lstSets.addGroup("Funny Sets");
lstSets.addGroup("Custom Sets"); lstSets.addGroup("Custom Sets");
lstSets.addGroup("Other Sets");
List<CardEdition> sets = FModel.getMagicDb().getSortedEditions(); List<CardEdition> sets = FModel.getMagicDb().getSortedEditions();
for (CardEdition set : sets) { for (CardEdition set : sets) {
@@ -156,36 +165,45 @@ public abstract class FormatFilter<T extends InventoryItem> extends ItemFilter<T
case EXPANSION: case EXPANSION:
lstSets.addItem(set, 1); lstSets.addItem(set, 1);
break; break;
case DUEL_DECKS: case STARTER:
lstSets.addItem(set, 2); lstSets.addItem(set, 2);
break; break;
case FROM_THE_VAULT: case REPRINT:
lstSets.addItem(set, 3); lstSets.addItem(set, 3);
break; break;
case PREMIUM_DECK_SERIES: case BOXED_SET:
lstSets.addItem(set,4); lstSets.addItem(set,4);
break; break;
case REPRINT: case COLLECTOR_EDITION:
lstSets.addItem(set, 5); lstSets.addItem(set, 5);
break; break;
case STARTER: case DUEL_DECK:
lstSets.addItem(set, 6); lstSets.addItem(set, 6);
break; break;
case PROMOS: case PROMO:
lstSets.addItem(set, 7); lstSets.addItem(set, 7);
break; break;
case ONLINE: case ONLINE:
lstSets.addItem(set, 8); lstSets.addItem(set, 8);
break; break;
case FUNNY: case DRAFT:
lstSets.addItem(set, 9); lstSets.addItem(set, 9);
break; break;
case THIRDPARTY: case COMMANDER:
lstSets.addItem(set, 10); lstSets.addItem(set, 10);
break; break;
default: case MULTIPLAYER:
lstSets.addItem(set, 11); lstSets.addItem(set, 11);
break; break;
case OTHER:
lstSets.addItem(set, 12);
break;
case FUNNY:
lstSets.addItem(set, 13);
break;
default: // THIRDPARTY - Custom Sets
lstSets.addItem(set, 14);
break;
} }
} }

View File

@@ -27,16 +27,16 @@ public class HistoricFormatSelect extends FScreen {
private GameFormat selectedFormat; private GameFormat selectedFormat;
private final FGroupList<GameFormat> lstFormats = add(new FGroupList<>()); private final FGroupList<GameFormat> lstFormats = add(new FGroupList<>());
private final Set<GameFormat.FormatSubType> historicSubTypes = new HashSet<>(Arrays.asList(GameFormat.FormatSubType.Block, private final Set<GameFormat.FormatSubType> historicSubTypes = new HashSet<>(Arrays.asList(GameFormat.FormatSubType.BLOCK,
GameFormat.FormatSubType.Standard,GameFormat.FormatSubType.Extended,GameFormat.FormatSubType.Modern, GameFormat.FormatSubType.STANDARD,GameFormat.FormatSubType.EXTENDED,GameFormat.FormatSubType.MODERN,
GameFormat.FormatSubType.Legacy, GameFormat.FormatSubType.Vintage)); GameFormat.FormatSubType.LEGACY, GameFormat.FormatSubType.VINTAGE));
private Runnable onCloseCallBack; private Runnable onCloseCallBack;
public HistoricFormatSelect() { public HistoricFormatSelect() {
super(Localizer.getInstance().getMessage("lblChooseFormat")); super(Localizer.getInstance().getMessage("lblChooseFormat"));
for (GameFormat.FormatType group:GameFormat.FormatType.values()){ for (GameFormat.FormatType group:GameFormat.FormatType.values()){
if (group == GameFormat.FormatType.Historic){ if (group == GameFormat.FormatType.HISTORIC){
for (GameFormat.FormatSubType subgroup:GameFormat.FormatSubType.values()){ for (GameFormat.FormatSubType subgroup:GameFormat.FormatSubType.values()){
if (historicSubTypes.contains(subgroup)){ if (historicSubTypes.contains(subgroup)){
lstFormats.addGroup(group.name() + "-" + subgroup.name()); lstFormats.addGroup(group.name() + "-" + subgroup.name());
@@ -48,39 +48,39 @@ public class HistoricFormatSelect extends FScreen {
} }
for (GameFormat format: FModel.getFormats().getOrderedList()){ for (GameFormat format: FModel.getFormats().getOrderedList()){
switch(format.getFormatType()){ switch(format.getFormatType()){
case Sanctioned: case SANCTIONED:
lstFormats.addItem(format, 0); lstFormats.addItem(format, 0);
break; break;
case Casual: case CASUAL:
lstFormats.addItem(format, 1); lstFormats.addItem(format, 1);
break; break;
case Historic: case HISTORIC:
switch (format.getFormatSubType()){ switch (format.getFormatSubType()){
case Block: case BLOCK:
lstFormats.addItem(format, 2); lstFormats.addItem(format, 2);
break; break;
case Standard: case STANDARD:
lstFormats.addItem(format, 3); lstFormats.addItem(format, 3);
break; break;
case Extended: case EXTENDED:
lstFormats.addItem(format, 4); lstFormats.addItem(format, 4);
break; break;
case Modern: case MODERN:
lstFormats.addItem(format, 5); lstFormats.addItem(format, 5);
break; break;
case Legacy: case LEGACY:
lstFormats.addItem(format, 6); lstFormats.addItem(format, 6);
break; break;
case Vintage: case VINTAGE:
lstFormats.addItem(format, 7); lstFormats.addItem(format, 7);
break; break;
} }
break; break;
case Digital: case DIGITAL:
lstFormats.addItem(format, 8); lstFormats.addItem(format, 8);
break; break;
case Custom: case CUSTOM:
lstFormats.addItem(format, 9); lstFormats.addItem(format, 9);
} }
} }

View File

@@ -2,7 +2,7 @@
Code=P15A Code=P15A
Date=2008-04-01 Date=2008-04-01
Name=15th Anniversary Cards Name=15th Anniversary Cards
Type=Promos Type=Promo
ScryfallCode=P15A ScryfallCode=P15A
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PL21 Code=PL21
Date=2021-01-25 Date=2021-01-25
Name=2021 Lunar New Year Name=2021 Lunar New Year
Type=Reprint Type=Promo
ScryfallCode=PL21 ScryfallCode=PL21
[cards] [cards]

View File

@@ -1,8 +1,8 @@
[metadata] [metadata]
Code=PAER Code=PAER
Date=2017-01-21 Date=2017-01-20
Name=Aether Revolt Promos Name=Aether Revolt Promos
Type=Promos Type=Promo
ScryfallCode=PAER ScryfallCode=PAER
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PARB Code=PARB
Date=2009-04-30 Date=2009-04-30
Name=Alara Reborn Promos Name=Alara Reborn Promos
Type=Promos Type=Promo
ScryfallCode=PARB ScryfallCode=PARB
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAKH Code=PAKH
Date=2017-04-28 Date=2017-04-28
Name=Amonkhet Promos Name=Amonkhet Promos
Type=Promos Type=Promo
ScryfallCode=PAKH ScryfallCode=PAKH
[cards] [cards]

View File

@@ -1,9 +1,9 @@
[metadata] [metadata]
Code=ATH Code=ATH
MciCode=at MciCode=at
Date=1998-11 Date=1998-11-01
Name=Anthologies Name=Anthologies
Type=Other Type=Boxed_Set
Border=White Border=White
Foil=NotSupported Foil=NotSupported
ScryfallCode=ATH ScryfallCode=ATH

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
Code=ATQ Code=ATQ
Date=1994-03-01 Date=1994-03-04
Name=Antiquities Name=Antiquities
Code2=AQ Code2=AQ
MciCode=aq MciCode=aq

View File

@@ -2,7 +2,7 @@
Code=PAPC Code=PAPC
Date=2001-06-04 Date=2001-06-04
Name=Apocalypse Promos Name=Apocalypse Promos
Type=Promos Type=Promo
CardLang=sa CardLang=sa
ScryfallCode=PAPC ScryfallCode=PAPC

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
Code=ARN Code=ARN
Date=1993-12-21 Date=1993-12-17
Name=Arabian Nights Name=Arabian Nights
Code2=AN Code2=AN
MciCode=an MciCode=an

View File

@@ -1,10 +1,10 @@
[metadata] [metadata]
Code=E01 Code=E01
Date=2017-06-17 Date=2017-06-16
Name=Archenemy: Nicol Bolas Name=Archenemy: Nicol Bolas
Code2=E01 Code2=E01
MciCode=E01 MciCode=E01
Type=Other Type=Multiplayer
ScryfallCode=E01 ScryfallCode=E01
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2010-06-18
Name=Archenemy Name=Archenemy
Code2=ARC Code2=ARC
MciCode=arc MciCode=arc
Type=Other Type=Multiplayer
ScryfallCode=ARC ScryfallCode=ARC
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PARL Code=PARL
Date=1996-08-02 Date=1996-08-02
Name=Arena League 1996 Name=Arena League 1996
Type=Promos Type=Promo
ScryfallCode=PARL ScryfallCode=PARL
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL99 Code=PAL99
Date=1999-01-01 Date=1999-01-01
Name=Arena League 1999 Name=Arena League 1999
Type=Promos Type=Promo
ScryfallCode=PAL99 ScryfallCode=PAL99
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL00 Code=PAL00
Date=2000-01-01 Date=2000-01-01
Name=Arena League 2000 Name=Arena League 2000
Type=Promos Type=Promo
ScryfallCode=PAL00 ScryfallCode=PAL00
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL01 Code=PAL01
Date=2001-01-01 Date=2001-01-01
Name=Arena League 2001 Name=Arena League 2001
Type=Promos Type=Promo
ScryfallCode=PAL01 ScryfallCode=PAL01
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL02 Code=PAL02
Date=2002-01-01 Date=2002-01-01
Name=Arena League 2002 Name=Arena League 2002
Type=Promos Type=Promo
ScryfallCode=PAL02 ScryfallCode=PAL02
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL03 Code=PAL03
Date=2003-01-01 Date=2003-01-01
Name=Arena League 2003 Name=Arena League 2003
Type=Promos Type=Promo
ScryfallCode=PAL03 ScryfallCode=PAL03
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL04 Code=PAL04
Date=2004-01-01 Date=2004-01-01
Name=Arena League 2004 Name=Arena League 2004
Type=Promos Type=Promo
ScryfallCode=PAL04 ScryfallCode=PAL04
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL05 Code=PAL05
Date=2005-01-01 Date=2005-01-01
Name=Arena League 2005 Name=Arena League 2005
Type=Promos Type=Promo
ScryfallCode=PAL05 ScryfallCode=PAL05
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAL06 Code=PAL06
Date=2006-01-01 Date=2006-01-01
Name=Arena League 2006 Name=Arena League 2006
Type=Promos Type=Promo
ScryfallCode=PAL06 ScryfallCode=PAL06
[cards] [cards]

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
Code=ANA Code=ANA
Date=2018-07-12 Date=2018-07-14
Name=Arena New Player Experience Name=Arena New Player Experience
Type=Online Type=Online
ScryfallCode=ANA ScryfallCode=ANA

View File

@@ -1,8 +1,8 @@
[metadata] [metadata]
Code=ARENA Code=ARENA
Date=1994-09-26 Date=1994-09-01
Name=Arena Name=Arena
Type=Other Type=Promo
ScryfallCode=PHPR ScryfallCode=PHPR
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PALP Code=PALP
Date=1998-09-01 Date=1998-09-01
Name=Asia Pacific Land Program Name=Asia Pacific Land Program
Type=Promos Type=Promo
ScryfallCode=PALP ScryfallCode=PALP
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PAVR Code=PAVR
Date=2012-04-28 Date=2012-04-28
Name=Avacyn Restored Promos Name=Avacyn Restored Promos
Type=Promos Type=Promo
ScryfallCode=PAVR ScryfallCode=PAVR
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PSS1 Code=PSS1
Date=2015-10-02 Date=2015-10-02
Name=BFZ Standard Series Name=BFZ Standard Series
Type=Reprint Type=Promo
ScryfallCode=PSS1 ScryfallCode=PSS1
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=BRB
Date=1999-11-12 Date=1999-11-12
Name=Battle Royale Name=Battle Royale
MciCode=br MciCode=br
Type=Other Type=Boxed_Set
Border=White Border=White
Foil=NotSupported Foil=NotSupported
ScryfallCode=BRB ScryfallCode=BRB

View File

@@ -1,8 +1,8 @@
[metadata] [metadata]
Code=PBFZ Code=PBFZ
Date=2015-10-03 Date=2015-10-02
Name=Battle for Zendikar Promos Name=Battle for Zendikar Promos
Type=Promos Type=Promo
ScryfallCode=PBFZ ScryfallCode=PBFZ
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=BBD
Date=2018-06-08 Date=2018-06-08
Name=Battlebond Name=Battlebond
MciCode=bbd MciCode=bbd
Type=Other Type=Draft
BoosterCovers=3 BoosterCovers=3
Booster=10 Common, 3 Uncommon, 1 fromSheet("BBD RareMythic"), 1 BasicLand Booster=10 Common, 3 Uncommon, 1 fromSheet("BBD RareMythic"), 1 BasicLand
ScryfallCode=BBD ScryfallCode=BBD

View File

@@ -3,7 +3,7 @@ Code=BTD
Date=2000-10-01 Date=2000-10-01
Name=Beatdown Name=Beatdown
MciCode=bd MciCode=bd
Type=Other Type=Boxed_Set
Border=White Border=White
Foil=NotSupported Foil=NotSupported
ScryfallCode=BTD ScryfallCode=BTD

View File

@@ -2,7 +2,7 @@
Code=PBOK Code=PBOK
Date=2005-02-04 Date=2005-02-04
Name=Betrayers of Kamigawa Promos Name=Betrayers of Kamigawa Promos
Type=Promos Type=Promo
ScryfallCode=PBOK ScryfallCode=PBOK
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PBNG Code=PBNG
Date=2014-02-01 Date=2014-02-01
Name=Born of the Gods Promos Name=Born of the Gods Promos
Type=Promos Type=Promo
ScryfallCode=PBNG ScryfallCode=PBNG
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PCEL Code=PCEL
Date=1996-08-14 Date=1996-08-14
Name=Celebration Cards Name=Celebration Cards
Type=Funny Type=Promo
ScryfallCode=PCEL ScryfallCode=PCEL
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PCHK Code=PCHK
Date=2004-10-01 Date=2004-10-01
Name=Champions of Kamigawa Promos Name=Champions of Kamigawa Promos
Type=Promos Type=Promo
ScryfallCode=PCHK ScryfallCode=PCHK
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PCMP Code=PCMP
Date=2006-03-18 Date=2006-03-18
Name=Champs and States Name=Champs and States
Type=Promos Type=Promo
ScryfallCode=PCMP ScryfallCode=PCMP
[cards] [cards]

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
Code=CHR Code=CHR
Date=1995-07 Date=1995-07-01
Name=Chronicles Name=Chronicles
Code2=CH Code2=CH
MciCode=ch MciCode=ch

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
Code=6ED Code=6ED
Date=1999-04-28 Date=1999-04-21
Name=Classic Sixth Edition Name=Classic Sixth Edition
Code2=6E Code2=6E
MciCode=6e MciCode=6e

View File

@@ -2,7 +2,7 @@
Code=PCSP Code=PCSP
Date=2006-07-21 Date=2006-07-21
Name=Coldsnap Promos Name=Coldsnap Promos
Type=Promos Type=Promo
ScryfallCode=PCSP ScryfallCode=PCSP
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=CST
Date=2006-07-21 Date=2006-07-21
Name=Coldsnap Theme Decks Name=Coldsnap Theme Decks
MciCode=cst MciCode=cst
Type=Other Type=Boxed_Set
ScryfallCode=CST ScryfallCode=CST
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2013-11-01
Name=Commander 2013 Name=Commander 2013
Code2=C13 Code2=C13
MciCode=c13 MciCode=c13
Type=Other Type=Commander
ScryfallCode=C13 ScryfallCode=C13
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2014-11-07
Name=Commander 2014 Name=Commander 2014
Code2=C14 Code2=C14
MciCode=c14 MciCode=c14
Type=Other Type=Commander
ScryfallCode=C14 ScryfallCode=C14
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2015-11-13
Name=Commander 2015 Name=Commander 2015
Code2=C15 Code2=C15
MciCode=c15 MciCode=c15
Type=Other Type=Commander
ScryfallCode=C15 ScryfallCode=C15
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2016-11-11
Name=Commander 2016 Name=Commander 2016
Code2=C16 Code2=C16
MciCode=c16 MciCode=c16
Type=Other Type=Commander
ScryfallCode=C16 ScryfallCode=C16
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2017-08-25
Name=Commander 2017 Name=Commander 2017
Code2=C17 Code2=C17
MciCode=c17 MciCode=c17
Type=Other Type=Commander
ScryfallCode=C17 ScryfallCode=C17
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2018-08-09
Name=Commander 2018 Name=Commander 2018
Code2=C18 Code2=C18
MciCode=c18 MciCode=c18
Type=Other Type=Commander
ScryfallCode=C18 ScryfallCode=C18
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2019-08-23
Name=Commander 2019 Name=Commander 2019
Code2=C19 Code2=C19
MciCode=c19 MciCode=c19
Type=Other Type=Commander
ScryfallCode=C19 ScryfallCode=C19
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=C20 Code=C20
Date=2020-04-17 Date=2020-04-17
Name=Commander 2020 Name=Commander 2020
Type=Other Type=Commander
ScryfallCode=C20 ScryfallCode=C20
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=C21 Code=C21
Date=2021-04-23 Date=2021-04-23
Name=Commander 2021 Name=Commander 2021
Type=Other Type=Commander
ScryfallCode=C21 ScryfallCode=C21
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=CM2
Date=2018-06-08 Date=2018-06-08
Name=Commander Anthology Vol. II Name=Commander Anthology Vol. II
MciCode=CM2 MciCode=CM2
Type=Other Type=Commander
ScryfallCode=CM2 ScryfallCode=CM2
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2017-06-09
Name=Commander Anthology Name=Commander Anthology
Code2=CMA Code2=CMA
MciCode=CMA MciCode=CMA
Type=Other Type=Commander
ScryfallCode=CMA ScryfallCode=CMA
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=CC1 Code=CC1
Date=2020-12-04 Date=2020-12-04
Name=Commander Collection Green Name=Commander Collection Green
Type=Reprint Type=Collector_Edition
ScryfallCode=CC1 ScryfallCode=CC1
[cards] [cards]

View File

@@ -1,8 +1,8 @@
[metadata] [metadata]
Code=CMR Code=CMR
Date=2020-11-11 Date=2020-11-20
Name=Commander Legends Name=Commander Legends
Type=Other Type=Draft
Booster=13 Common:fromSheet("CMR cards"), 3 fromSheet("CMR Non-Legendary Uncommons"), 1 fromSheet("CMR Non-Legendary RareMythics"), 1 fromSheet("CMR Foils"), 2 fromSheet("CMR Legendaries") Booster=13 Common:fromSheet("CMR cards"), 3 fromSheet("CMR Non-Legendary Uncommons"), 1 fromSheet("CMR Non-Legendary RareMythics"), 1 fromSheet("CMR Foils"), 2 fromSheet("CMR Legendaries")
Foil=NotSupported Foil=NotSupported
DoublePick=Always DoublePick=Always

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
Code=TD0 Code=TD0
Date=2009-08-25 Date=2010-11-08
Name=Commander Theme Decks Name=Commander Theme Decks
MciCode=td0 MciCode=td0
Type=Online Type=Online

View File

@@ -5,6 +5,7 @@ Date=2012-11-02
Name=Commander's Arsenal Name=Commander's Arsenal
ScryfallCode=CM1 ScryfallCode=CM1
Type=Commander
[cards] [cards]
1 R Chaos Warp 1 R Chaos Warp
2 C Command Tower 2 C Command Tower

View File

@@ -5,7 +5,7 @@ Name=Commander
Code2=COM Code2=COM
Alias=CMD Alias=CMD
MciCode=cmd MciCode=cmd
Type=Other Type=Commander
BoosterBox=0 BoosterBox=0
ScryfallCode=cmd ScryfallCode=cmd

View File

@@ -2,7 +2,7 @@
Code=PCON Code=PCON
Date=2009-02-06 Date=2009-02-06
Name=Conflux Promos Name=Conflux Promos
Type=Promos Type=Promo
ScryfallCode=PCON ScryfallCode=PCON
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PCNS Code=PCNS
Date=2014-06-06 Date=2014-06-06
Name=Conspiracy Promos Name=Conspiracy Promos
Type=Promos Type=Promo
ScryfallCode=PCNS ScryfallCode=PCNS
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2016-08-26
Name=Conspiracy: Take the Crown Name=Conspiracy: Take the Crown
Code2=CN2 Code2=CN2
MciCode=cn2 MciCode=cn2
Type=Other Type=Draft
BoosterCovers=3 BoosterCovers=3
Booster=10 Common:!fromSheet("CN2 Not In Normal Slots"), 3 Uncommon:!fromSheet("CN2 Not In Normal Slots"), 1 RareMythic:!fromSheet("CN2 Not In Normal Slots"), 1 fromSheet("CN2 Draft Matters") Booster=10 Common:!fromSheet("CN2 Not In Normal Slots"), 3 Uncommon:!fromSheet("CN2 Not In Normal Slots"), 1 RareMythic:!fromSheet("CN2 Not In Normal Slots"), 1 fromSheet("CN2 Draft Matters")
AdditionalSheetForFoils=fromSheet("CN2 Foil Kaya") AdditionalSheetForFoils=fromSheet("CN2 Foil Kaya")

View File

@@ -4,7 +4,7 @@ Date=2014-06-06
Name=Conspiracy Name=Conspiracy
Code2=CNS Code2=CNS
MciCode=cns MciCode=cns
Type=Other Type=Draft
BoosterCovers=3 BoosterCovers=3
Booster=10 Common:!fromSheet("CNS Draft Matters"), 3 Uncommon:!fromSheet("CNS Draft Matters"), 1 RareMythic:!fromSheet("CNS Draft Matters"), 1 fromSheet("CNS Draft Matters") Booster=10 Common:!fromSheet("CNS Draft Matters"), 3 Uncommon:!fromSheet("CNS Draft Matters"), 1 RareMythic:!fromSheet("CNS Draft Matters"), 1 fromSheet("CNS Draft Matters")
ScryfallCode=CNS ScryfallCode=CNS

View File

@@ -2,7 +2,7 @@
Code=PM19 Code=PM19
Date=2018-07-13 Date=2018-07-13
Name=Core Set 2019 Promos Name=Core Set 2019 Promos
Type=Promos Type=Promo
ScryfallCode=PM19 ScryfallCode=PM19
[cards] [cards]

View File

@@ -1,8 +1,8 @@
[metadata] [metadata]
Code=PM20 Code=PM20
Date=2019-07-02 Date=2019-07-12
Name=Core Set 2020 Promos Name=Core Set 2020 Promos
Type=Promos Type=Promo
ScryfallCode=PM20 ScryfallCode=PM20
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PLGM Code=PLGM
Date=1995-01-01 Date=1995-01-01
Name=DCI Legend Membership Name=DCI Legend Membership
Type=Promos Type=Promo
ScryfallCode=PLGM ScryfallCode=PLGM
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PDKA Code=PDKA
Date=2012-01-28 Date=2012-01-28
Name=Dark Ascension Promos Name=Dark Ascension Promos
Type=Promos Type=Promo
ScryfallCode=PDKA ScryfallCode=PDKA
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PDST Code=PDST
Date=2004-06-04 Date=2004-06-04
Name=Darksteel Promos Name=Darksteel Promos
Type=Promos Type=Promo
ScryfallCode=PDST ScryfallCode=PDST
[cards] [cards]

View File

@@ -1,9 +1,9 @@
[metadata] [metadata]
Code=DKM Code=DKM
Date=2001-12 Date=2001-12-01
Name=Deckmasters: Garfield vs. Finkel Name=Deckmasters: Garfield vs. Finkel
MciCode=dm MciCode=dm
Type=Other Type=Boxed_Set
Border=White Border=White
ScryfallCode=DKM ScryfallCode=DKM

View File

@@ -2,7 +2,7 @@
Code=PDIS Code=PDIS
Date=2006-05-05 Date=2006-05-05
Name=Dissension Promos Name=Dissension Promos
Type=Promos Type=Promo
ScryfallCode=PDIS ScryfallCode=PDIS
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PDOM Code=PDOM
Date=2018-04-27 Date=2018-04-27
Name=Dominaria Promos Name=Dominaria Promos
Type=Promos Type=Promo
ScryfallCode=PDOM ScryfallCode=PDOM
[cards] [cards]

View File

@@ -2,7 +2,7 @@
Code=PDGM Code=PDGM
Date=2013-04-27 Date=2013-04-27
Name=Dragon's Maze Promos Name=Dragon's Maze Promos
Type=Promos Type=Promo
ScryfallCode=PDGM ScryfallCode=PDGM
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DRC94
Date=1994-07-15 Date=1994-07-15
Name=DragonCon 1994 Name=DragonCon 1994
Border=Black Border=Black
Type=Other Type=Promo
ScryfallCode=PDRC ScryfallCode=PDRC
[cards] [cards]

View File

@@ -1,8 +1,8 @@
[metadata] [metadata]
Code=PDTK Code=PDTK
Date=2015-03-08 Date=2015-03-27
Name=Dragons of Tarkir Promos Name=Dragons of Tarkir Promos
Type=Promos Type=Promo
ScryfallCode=PDTK ScryfallCode=PDTK
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DDH
Date=2011-09-02 Date=2011-09-02
Name=Duel Decks: Ajani vs. Nicol Bolas Name=Duel Decks: Ajani vs. Nicol Bolas
MciCode=ddh MciCode=ddh
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDH ScryfallCode=DDH
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DVD
Date=2014-12-05 Date=2014-12-05
Name=Duel Decks Anthology: Divine vs. Demonic Name=Duel Decks Anthology: Divine vs. Demonic
MciCode=dvd MciCode=dvd
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DVD ScryfallCode=DVD
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=EVG
Date=2014-12-05 Date=2014-12-05
Name=Duel Decks Anthology: Elves vs. Goblins Name=Duel Decks Anthology: Elves vs. Goblins
MciCode=evg MciCode=evg
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=EVG ScryfallCode=EVG
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=GVL
Date=2014-12-05 Date=2014-12-05
Name=Duel Decks Anthology: Garruk vs. Liliana Name=Duel Decks Anthology: Garruk vs. Liliana
MciCode=gvl MciCode=gvl
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=GVL ScryfallCode=GVL
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=JVC
Date=2014-12-05 Date=2014-12-05
Name=Duel Decks Anthology: Jace vs. Chandra Name=Duel Decks Anthology: Jace vs. Chandra
MciCode=jvc MciCode=jvc
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=JVC ScryfallCode=JVC
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2016-02-26
Name=Duel Decks: Blessed vs. Cursed Name=Duel Decks: Blessed vs. Cursed
Code2=DDQ Code2=DDQ
MciCode=ddq MciCode=ddq
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDQ ScryfallCode=DDQ
[cards] [cards]

View File

@@ -4,7 +4,7 @@ Date=2009-04-10
Name=Duel Decks: Divine vs. Demonic Name=Duel Decks: Divine vs. Demonic
Code2=DDC Code2=DDC
MciCode=dvd MciCode=dvd
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDC ScryfallCode=DDC
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DDO
Date=2015-02-27 Date=2015-02-27
Name=Duel Decks: Elspeth vs. Kiora Name=Duel Decks: Elspeth vs. Kiora
MciCode=ddo MciCode=ddo
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDO ScryfallCode=DDO
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DDF
Date=2010-09-03 Date=2010-09-03
Name=Duel Decks: Elspeth vs. Tezzeret Name=Duel Decks: Elspeth vs. Tezzeret
MciCode=ddf MciCode=ddf
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDF ScryfallCode=DDF
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DD1
Date=2007-11-16 Date=2007-11-16
Name=Duel Decks: Elves vs. Goblins Name=Duel Decks: Elves vs. Goblins
MciCode=dd1 MciCode=dd1
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DD1 ScryfallCode=DD1
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DDU
Date=2018-04-06 Date=2018-04-06
Name=Duel Decks: Elves vs. Inventors Name=Duel Decks: Elves vs. Inventors
MciCode=ddu MciCode=ddu
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDU ScryfallCode=DDU
[cards] [cards]

View File

@@ -3,7 +3,7 @@ Code=DDD
Date=2009-10-30 Date=2009-10-30
Name=Duel Decks: Garruk vs. Liliana Name=Duel Decks: Garruk vs. Liliana
MciCode=gvl MciCode=gvl
Type=Duel_Decks Type=Duel_Deck
ScryfallCode=DDD ScryfallCode=DDD
[cards] [cards]

Some files were not shown because too many files have changed in this diff Show More