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;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
import com.google.common.base.Predicate;
import forge.card.CardDb;
import forge.card.*;
import forge.card.CardDb.CardRequest;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.card.PrintSheet;
import forge.item.BoosterBox;
import forge.item.FatPack;
import forge.item.PaperCard;
@@ -75,7 +67,7 @@ public class StaticData {
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
this.blockDataFolder = blockDataFolder;
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;
lastInstance = this;
List<String> funnyCards = new ArrayList<>();
@@ -174,6 +166,22 @@ public class StaticData {
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){
CardEdition edition = this.editions.get(setCode);
if (edition == null) // try custom editions

View File

@@ -629,7 +629,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} catch (Exception ex) {
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;
try {
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;
} catch (Exception ex) {
return false;

View File

@@ -37,6 +37,7 @@ import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import forge.util.*;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Function;
@@ -52,11 +53,6 @@ import forge.card.CardDb.SetPreference;
import forge.deck.CardPool;
import forge.item.PaperCard;
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.StorageReaderBase;
import forge.util.storage.StorageReaderFolder;
@@ -78,19 +74,23 @@ public final class CardEdition implements Comparable<CardEdition> {
CORE,
EXPANSION,
REPRINT,
ONLINE,
STARTER,
REPRINT,
BOXED_SET,
DUEL_DECKS,
PREMIUM_DECK_SERIES,
FROM_THE_VAULT,
COLLECTOR_EDITION,
DUEL_DECK,
PROMO,
ONLINE,
OTHER,
PROMOS,
DRAFT,
COMMANDER,
MULTIPLAYER,
FUNNY,
THIRDPARTY; // custom sets
OTHER, // FALLBACK CATEGORY
CUSTOM_SET; // custom sets
public String getBoosterBoxDefault() {
switch (this) {
@@ -101,6 +101,19 @@ public final class CardEdition implements Comparable<CardEdition> {
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 {
@@ -476,8 +489,16 @@ public final class CardEdition implements Comparable<CardEdition> {
}
public static class Reader extends StorageReaderFolder<CardEdition> {
private boolean isCustomEditions;
public Reader(File path) {
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
@@ -596,15 +617,20 @@ public final class CardEdition implements Comparable<CardEdition> {
res.alias = section.get("alias");
res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH));
String type = section.get("type");
Type enumType = Type.UNKNOWN;
if (null != type && !type.isEmpty()) {
try {
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException ignored) {
// ignore; type will get UNKNOWN
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type);
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()) {
try {
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException ignored) {
// ignore; type will get UNKNOWN
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type);
}
}
}
res.type = enumType;
res.prerelease = section.get("Prerelease", null);

View File

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

View File

@@ -1,5 +1,7 @@
package forge.itemmanager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
@@ -11,21 +13,8 @@ import forge.gamemodes.quest.QuestWorld;
import forge.gamemodes.quest.data.QuestPreferences;
import forge.gui.GuiUtils;
import forge.item.PaperCard;
import forge.itemmanager.filters.AdvancedSearchFilter;
import forge.itemmanager.filters.CardCMCFilter;
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.itemmanager.filters.*;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.screens.home.quest.DialogChooseFormats;
import forge.screens.home.quest.DialogChooseSets;
@@ -162,6 +151,28 @@ public class CardManager extends ItemManager<PaperCard> {
}
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.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.Rectangle;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.JMenu;
@@ -14,6 +12,8 @@ import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import forge.itemmanager.filters.*;
import forge.localinstance.properties.ForgePreferences;
import org.apache.commons.lang3.StringUtils;
import forge.Singletons;
@@ -29,15 +29,6 @@ import forge.gui.GuiUtils;
import forge.gui.UiCommand;
import forge.gui.framework.FScreen;
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.ItemListView;
import forge.itemmanager.views.ItemTableColumn;
@@ -123,7 +114,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
/**
* 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) {
this.cmdDelete = c0;
@@ -132,7 +123,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
/**
* 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) {
this.cmdSelect = c0;
@@ -289,6 +280,28 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
}
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.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;
/**
* 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
* ItemManager Constructor
*/
protected ItemManager(final Class<T> genericType0, final CDetailPicture cDetailPicture, final boolean wantUnique0) {
this.cDetailPicture = cDetailPicture;
@@ -1043,9 +1039,7 @@ public abstract class ItemManager<T extends InventoryItem> extends JPanel implem
/**
*
* updateView.
*
* @param bForceFilter
* updateView
*/
public void updateView(final boolean forceFilter, final Iterable<T> itemsToSelect) {
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<>();
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0) {
this(itemManager0, true);
}
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, boolean allowReprints) {
super(itemManager0);
this.allowReprints = allowReprints;
}
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);
this.questWorlds.add(questWorld0);
this.formats.add(getQuestWorldFormat(questWorld0));
this.allowReprints = allowReprints;
}
@Override

View File

@@ -14,7 +14,7 @@ import forge.screens.home.quest.DialogChooseSets;
*
*/
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) {
super(itemManager0);
@@ -50,9 +50,9 @@ public class CardSetFilter extends CardFormatFilter {
}
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;
dialog.setWantReprintsCB(allowReprints);
dialog.setOkCallback(new Runnable() {
@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
protected final Predicate<DeckProxy> buildPredicate() {
protected Predicate<DeckProxy> buildPredicate() {
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.Set;
import com.google.common.base.Predicate;
import forge.deck.DeckProxy;
import forge.game.GameFormat;
import forge.itemmanager.ItemManager;
import forge.screens.home.quest.DialogChooseSets;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
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) {
super(itemManager0);
@@ -47,7 +49,8 @@ public class DeckSetFilter extends DeckFormatFilter {
}
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() {
@Override
public void run() {
@@ -74,4 +77,14 @@ public class DeckSetFilter extends DeckFormatFilter {
protected Iterable<String> getList() {
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());
box.setName(format.getName());
switch (format.getFormatType()){
case Sanctioned:
case SANCTIONED:
sanctioned.add(box);
break;
case Historic:
case HISTORIC:
historic.add(box);
break;
case Custom:
case Casual:
case Digital:
case CUSTOM:
case CASUAL:
case DIGITAL:
default:
casual.add(box);
break;

View File

@@ -1,40 +1,25 @@
package forge.screens.home.quest;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.function.Predicate;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.google.common.collect.Lists;
import forge.Singletons;
import forge.toolbox.*;
import forge.card.CardEdition;
import forge.game.GameFormat;
import forge.gui.SOverlayUtils;
import forge.localinstance.skin.FSkinProp;
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.TextUtil;
import net.miginfocom.swing.MigLayout;
import forge.toolbox.FCheckBoxTree.FTreeNode;
import forge.toolbox.FCheckBoxTree.FTreeNodeData;
public class DialogChooseSets {
@@ -42,312 +27,418 @@ public class DialogChooseSets {
private boolean wantReprints = true;
private Runnable okCallback;
private final List<FCheckBox> choices = new ArrayList<>();
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, boolean showWantReprintsCheckbox) {
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
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);
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
boolean showWantReprintsCheckbox, boolean allowReprints) {
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<>();
// get the map of each editions per type
Map<CardEdition.Type, List<CardEdition>> editionsTypeMap = FModel.getMagicDb().getEditionsTypeMap();
for (CardEdition ce : editions) {
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));
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code));
switch (ce.getType()) {
case CORE:
coreSets.add(box);
break;
case EXPANSION:
expansionSets.add(box);
break;
default:
otherSets.add(box);
break;
/* 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());
}
}
// CustomSet
List<FCheckBox> customSets = new ArrayList<>();
if (customSetsExist) {
for (CardEdition ce : customEditions){
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();
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code)));
box.setName(code);
box.setSelected(null != preselectedSets && preselectedSets.contains(code));
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code));
customSets.add(box);
boolean isSelected = null != preselectedSets && preselectedSets.contains(code);
boolean isEnabled = null == unselectableSets || !unselectableSets.contains(code);
FTreeNodeData editionNode = new FTreeNodeData(ce, ce.getName(), ce.getCode());
editionNode.isEnabled = isEnabled;
editionNode.isSelected = isSelected;
if (isEnabled)
enabledEditionsOfTypeCounter += 1;
editionPerTypeNodes.add(editionNode);
}
editionTypeTreeData.put(new FTreeNodeData(editionType), editionPerTypeNodes);
allEditionTypes.put(editionType, enabledEditionsOfTypeCounter);
}
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));
this.checkBoxTree.setTreeData(editionTypeTreeData);
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();
// === 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;
}
}
if (allZeros)
randomSelectionButton.setEnabled(false);
}
}
});
});
// == ADD SPINNERS AND LABELS TO THE PANEL ==
JPanel typeFieldsPanel = null;
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 ===
// ===================================================================
JPanel optionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2"));
optionsPanel.setVisible(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"));
leftOptionsPanel.setOpaque(false);
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectNumber") + ":").fontSize(14).fontStyle(Font.BOLD).build(), " span 2");
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblCore") + ":").build());
leftOptionsPanel.add(coreField, "w 40!");
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");
// === 2. FORMAT OPTIONS PANEL ===
// This will include a button for each format and a NO-Format Radio Button (default)
JPanel formatOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center"));
formatOptionsPanel.setOpaque(false);
formatOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") + ":")
.fontSize(14).fontStyle(Font.BOLD).build(), "span 1");
ButtonGroup formatButtonGroup = new ButtonGroup();
List<GameFormat> gameFormats = new ArrayList<>();
FModel.getFormats().getSanctionedList().forEach(gameFormats::add);
Map<String, FRadioButton> formatButtonGroupMap = new HashMap<>();
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());
button.setActionCommand(item.getName());
formatButtonGroup.add(button);
rightOptionsPanel.add(button);
}
});
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblModernCardFrame"));
button.setActionCommand("Modern Card Frame");
formatButtonGroup.add(button);
rightOptionsPanel.add(button);
FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction"));
noFormatSelectionButton.setActionCommand("No Format Restriction");
formatButtonGroup.add(noFormatSelectionButton);
rightOptionsPanel.add(noFormatSelectionButton);
noFormatSelectionButton.setSelected(true);
optionsPanel.add(leftOptionsPanel, "w 33%:40%:78%");
optionsPanel.add(rightOptionsPanel, "w 33%:60%:78%");
FButton randomSelectionButton = new FButton(Localizer.getInstance().getMessage("lblRandomizeSets"));
randomSelectionButton.addActionListener(actionEvent -> {
int numberOfCoreSets = Integer.parseInt(coreField.getText());
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);
FRadioButton button = new FRadioButton(item.getName());
button.setActionCommand(item.getName());
button.addActionListener(new ActionListener() {
@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;
}
}
}
List<CardEdition> filteredExpansionSets = new ArrayList<>();
for (CardEdition edition : editions) {
if (edition.getType() == CardEdition.Type.EXPANSION) {
if (formatPredicate != null && formatPredicate.test(edition)) {
filteredExpansionSets.add(edition);
/* 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);
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);
// == Update Checkbox Tree ==
for (String code : codesToDisable) {
FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
checkBoxTree.setNodeEnabledStatus(node, false);
}
}
}
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);
for (String code : allowedSetCodes) {
FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
checkBoxTree.setNodeEnabledStatus(node, true);
}
}
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);
// == 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 (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);
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);
}
}
}
}
panel.repaintSelf();
});
formatButtonGroup.add(button);
formatOptionsPanel.add(button);
formatButtonGroupMap.put(item.getName(), button);
});
FButton clearSelectionButton = new FButton(Localizer.getInstance().getMessage("lblClearSelection"));
clearSelectionButton.addActionListener(actionEvent -> {
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 cmSet : customSets) {
cmSet.setSelected(false);
// NO FORMAT Button
FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction"));
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);
}
}
}
panel.repaintSelf();
});
formatButtonGroup.add(noFormatSelectionButton);
formatOptionsPanel.add(noFormatSelectionButton);
formatButtonGroupMap.put("No Format", noFormatSelectionButton);
noFormatSelectionButton.setSelected(true);
// === Update Option Panel ===
optionsPanel.add(formatOptionsPanel, "span 2, w 100%");
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2");
// === EDITION (PER TYPE) SELECTION PANEL ===
// 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%");
// ======== ADD ACTION LISTENERS TO CLEAR AND RANDOM SELECT BUTTONS
clearSelectionButton.addActionListener(actionEvent -> {
this.checkBoxTree.resetCheckingState();
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);
}
// We can safely reset selections as this button would not be enabled at all
// if at least one spinner has been modified, and so countPerEdition updated.
checkBoxTree.resetCheckingState();
String selectedFormat = formatButtonGroup.getSelection().getActionCommand();
FRadioButton formatButton = formatButtonGroupMap.get(selectedFormat);
formatButton.doClick();
for (CardEdition.Type editionType : countPerEditionType.keySet()){
int totalToSelect = countPerEditionType.get(editionType);
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);
}
}
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"));
hideOptionsButton.addActionListener(actionEvent -> {
optionsPanel.setVisible(false);
showOptionsButton.setVisible(true);
});
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");
mainDialogPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets"))
.fontSize(20).build(), "center, span, wrap, gaptop 10");
mainDialogPanel.add(editionSelectionPanel, "aligny top, w 50%, span 1");
mainDialogPanel.add(randomSelectionPanel, "aligny top, w 50%, span 1");
mainDialogPanel.add(optionsPanel, "center, w 100, span 2");
final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
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"));
southPanel.setOpaque(false);
southPanel.add(btnOk, "center, w 200!, h 30!");
southPanel.add(btnCancel, "center, w 200!, h 30!");
southPanel.add(btnOk, "center, w 250!, 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);
panel.getRootPane().setDefaultButton(btnOk);
overlay.add(mainDialogPanel);
mainDialogPanel.getRootPane().setDefaultButton(btnOk);
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) {
okCallback = onOk;
}
@@ -401,46 +521,16 @@ public class DialogChooseSets {
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() {
for (FCheckBox box : choices) {
if (box.isSelected()) {
selectedSets.add(box.getName());
}
wantReprints = cbWantReprints.isSelected();
Object[] checkedValues = this.checkBoxTree.getCheckedValues(true);
for (Object data: checkedValues){
CardEdition edition = (CardEdition) data;
selectedSets.add(edition.getCode());
}
wantReprints = cbWantReprints.isSelected();
if (null != okCallback) {
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 step int
* @return {@link java.awt.CFaceolor}
* @return {@link java.awt.Color}
*/
public static Color stepColor(final Color clr0, final int step) {
int r = clr0.getRed();

View File

@@ -136,56 +136,74 @@ public abstract class FormatFilter<T extends InventoryItem> extends ItemFilter<T
lstSets.addGroup("Core Sets");
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("Reprint Sets");
lstSets.addGroup("Boxed Sets");
lstSets.addGroup("Collector's Edition");
lstSets.addGroup("Duel Decks");
lstSets.addGroup("Promo 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("Custom Sets");
lstSets.addGroup("Other Sets");
List<CardEdition> sets = FModel.getMagicDb().getSortedEditions();
for (CardEdition set : sets) {
switch (set.getType()) {
case CORE:
lstSets.addItem(set, 0);
break;
case EXPANSION:
lstSets.addItem(set, 1);
break;
case DUEL_DECKS:
lstSets.addItem(set, 2);
break;
case FROM_THE_VAULT:
lstSets.addItem(set, 3);
break;
case PREMIUM_DECK_SERIES:
lstSets.addItem(set, 4);
break;
case REPRINT:
lstSets.addItem(set, 5);
break;
case STARTER:
lstSets.addItem(set, 6);
break;
case PROMOS:
lstSets.addItem(set, 7);
break;
case ONLINE:
lstSets.addItem(set, 8);
break;
case FUNNY:
lstSets.addItem(set, 9);
break;
case THIRDPARTY:
lstSets.addItem(set, 10);
break;
default:
lstSets.addItem(set, 11);
break;
case CORE:
lstSets.addItem(set, 0);
break;
case EXPANSION:
lstSets.addItem(set, 1);
break;
case STARTER:
lstSets.addItem(set, 2);
break;
case REPRINT:
lstSets.addItem(set, 3);
break;
case BOXED_SET:
lstSets.addItem(set,4);
break;
case COLLECTOR_EDITION:
lstSets.addItem(set, 5);
break;
case DUEL_DECK:
lstSets.addItem(set, 6);
break;
case PROMO:
lstSets.addItem(set, 7);
break;
case ONLINE:
lstSets.addItem(set, 8);
break;
case DRAFT:
lstSets.addItem(set, 9);
break;
case COMMANDER:
lstSets.addItem(set, 10);
break;
case MULTIPLAYER:
lstSets.addItem(set, 11);
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 final FGroupList<GameFormat> lstFormats = add(new FGroupList<>());
private final Set<GameFormat.FormatSubType> historicSubTypes = new HashSet<>(Arrays.asList(GameFormat.FormatSubType.Block,
GameFormat.FormatSubType.Standard,GameFormat.FormatSubType.Extended,GameFormat.FormatSubType.Modern,
GameFormat.FormatSubType.Legacy, GameFormat.FormatSubType.Vintage));
private final Set<GameFormat.FormatSubType> historicSubTypes = new HashSet<>(Arrays.asList(GameFormat.FormatSubType.BLOCK,
GameFormat.FormatSubType.STANDARD,GameFormat.FormatSubType.EXTENDED,GameFormat.FormatSubType.MODERN,
GameFormat.FormatSubType.LEGACY, GameFormat.FormatSubType.VINTAGE));
private Runnable onCloseCallBack;
public HistoricFormatSelect() {
super(Localizer.getInstance().getMessage("lblChooseFormat"));
for (GameFormat.FormatType group:GameFormat.FormatType.values()){
if (group == GameFormat.FormatType.Historic){
if (group == GameFormat.FormatType.HISTORIC){
for (GameFormat.FormatSubType subgroup:GameFormat.FormatSubType.values()){
if (historicSubTypes.contains(subgroup)){
lstFormats.addGroup(group.name() + "-" + subgroup.name());
@@ -48,39 +48,39 @@ public class HistoricFormatSelect extends FScreen {
}
for (GameFormat format: FModel.getFormats().getOrderedList()){
switch(format.getFormatType()){
case Sanctioned:
case SANCTIONED:
lstFormats.addItem(format, 0);
break;
case Casual:
case CASUAL:
lstFormats.addItem(format, 1);
break;
case Historic:
case HISTORIC:
switch (format.getFormatSubType()){
case Block:
case BLOCK:
lstFormats.addItem(format, 2);
break;
case Standard:
case STANDARD:
lstFormats.addItem(format, 3);
break;
case Extended:
case EXTENDED:
lstFormats.addItem(format, 4);
break;
case Modern:
case MODERN:
lstFormats.addItem(format, 5);
break;
case Legacy:
case LEGACY:
lstFormats.addItem(format, 6);
break;
case Vintage:
case VINTAGE:
lstFormats.addItem(format, 7);
break;
}
break;
case Digital:
case DIGITAL:
lstFormats.addItem(format, 8);
break;
case Custom:
case CUSTOM:
lstFormats.addItem(format, 9);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
[metadata]
Code=CMR
Date=2020-11-11
Date=2020-11-20
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")
Foil=NotSupported
DoublePick=Always

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ Date=2016-08-26
Name=Conspiracy: Take the Crown
Code2=CN2
MciCode=cn2
Type=Other
Type=Draft
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")
AdditionalSheetForFoils=fromSheet("CN2 Foil Kaya")

View File

@@ -4,7 +4,7 @@ Date=2014-06-06
Name=Conspiracy
Code2=CNS
MciCode=cns
Type=Other
Type=Draft
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")
ScryfallCode=CNS

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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