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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,40 +1,25 @@
package forge.screens.home.quest; package forge.screens.home.quest;
import java.awt.Font; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.*;
import java.awt.event.ActionListener; import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import javax.swing.ButtonGroup; import javax.swing.*;
import javax.swing.JPanel; import javax.swing.event.ChangeEvent;
import javax.swing.JSeparator; import javax.swing.event.ChangeListener;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import com.google.common.collect.Lists; import forge.Singletons;
import forge.toolbox.*;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.game.GameFormat; import forge.game.GameFormat;
import forge.gui.SOverlayUtils; import forge.gui.SOverlayUtils;
import forge.localinstance.skin.FSkinProp; import forge.localinstance.skin.FSkinProp;
import forge.model.FModel; import forge.model.FModel;
import forge.toolbox.FButton;
import forge.toolbox.FCheckBox;
import forge.toolbox.FCheckBoxList;
import forge.toolbox.FLabel;
import forge.toolbox.FOverlay;
import forge.toolbox.FPanel;
import forge.toolbox.FRadioButton;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.toolbox.FTextField;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.TextUtil;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import forge.toolbox.FCheckBoxTree.FTreeNode;
import forge.toolbox.FCheckBoxTree.FTreeNodeData;
public class DialogChooseSets { public class DialogChooseSets {
@@ -42,312 +27,418 @@ public class DialogChooseSets {
private boolean wantReprints = true; private boolean wantReprints = true;
private Runnable okCallback; private Runnable okCallback;
private final List<FCheckBox> choices = new ArrayList<>();
private final FCheckBox cbWantReprints = new FCheckBox(Localizer.getInstance().getMessage("lblDisplayRecentSetReprints")); private final FCheckBox cbWantReprints = new FCheckBox(Localizer.getInstance().getMessage("lblDisplayRecentSetReprints"));
private final FCheckBoxTree checkBoxTree = new FCheckBoxTree();
// lists are of set codes (e.g. "2ED") public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets, boolean showWantReprintsCheckbox) { boolean showWantReprintsCheckbox) {
this(preselectedSets, unselectableSets, showWantReprintsCheckbox, false);
}
// create a local copy of the editions list so we can sort it public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
List<CardEdition> editions = Lists.newArrayList(FModel.getMagicDb().getEditions()); boolean showWantReprintsCheckbox, boolean allowReprints) {
Collections.sort(editions);
Collections.reverse(editions);
List<CardEdition> customEditions = Lists.newArrayList(FModel.getMagicDb().getCustomEditions()); // get the map of each editions per type
boolean customSetsExist = (customEditions.size() > 0); Map<CardEdition.Type, List<CardEdition>> editionsTypeMap = FModel.getMagicDb().getEditionsTypeMap();
if (customSetsExist){
Collections.sort(customEditions);
Collections.reverse(customEditions);
}
List<FCheckBox> coreSets = new ArrayList<>();
List<FCheckBox> expansionSets = new ArrayList<>();
List<FCheckBox> otherSets = new ArrayList<>();
for (CardEdition ce : editions) { /* Gather all the different types among preselected and unselectable sets (if any)
String code = ce.getCode(); lists are of set codes (e.g. "2ED", edition.getCode())
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code)));
box.setName(code); NOTE: preselected SetTypes will be created as TreeSet as the ordering of Types will be used to
box.setSelected(null != preselectedSets && preselectedSets.contains(code)); decide which type in the UI list of types should be selected, in case of multiple types
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code)); available in preselected types (e.g. expansion and core, core will be selected as it comes first) */
switch (ce.getType()) { Set<CardEdition.Type> preselectedTypes = null;
case CORE: if (preselectedSets != null){
coreSets.add(box); preselectedTypes = new TreeSet<>();
break; for (String code: preselectedSets){
case EXPANSION: CardEdition edition = FModel.getMagicDb().getCardEdition(code);
expansionSets.add(box); preselectedTypes.add(edition.getType());
break;
default:
otherSets.add(box);
break;
} }
} }
// CustomSet
List<FCheckBox> customSets = new ArrayList<>(); TreeMap<FTreeNodeData, List<FTreeNodeData>> editionTypeTreeData = new TreeMap<>();
if (customSetsExist) { TreeMap<CardEdition.Type, Integer> allEditionTypes = new TreeMap<>();
for (CardEdition ce : customEditions){ List<CardEdition> allCardEditions = new ArrayList<>();
for (CardEdition.Type editionType : editionsTypeMap.keySet()) {
List<CardEdition> editionsOfType = editionsTypeMap.get(editionType);
if (editionsOfType.size() == 0) // skip empty set types
continue;
List<FTreeNodeData> editionPerTypeNodes = new ArrayList<>();
allCardEditions.addAll(editionsOfType);
int enabledEditionsOfTypeCounter = 0;
for (CardEdition ce: editionsOfType){
String code = ce.getCode(); String code = ce.getCode();
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code))); boolean isSelected = null != preselectedSets && preselectedSets.contains(code);
box.setName(code); boolean isEnabled = null == unselectableSets || !unselectableSets.contains(code);
box.setSelected(null != preselectedSets && preselectedSets.contains(code)); FTreeNodeData editionNode = new FTreeNodeData(ce, ce.getName(), ce.getCode());
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code)); editionNode.isEnabled = isEnabled;
customSets.add(box); 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; this.checkBoxTree.setTreeData(editionTypeTreeData);
FPanel panel = new FPanel(new MigLayout(String.format("insets 10, gap 5, center, wrap %d", wrapCol)));
panel.setOpaque(false);
panel.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
FTextField coreField = new FTextField.Builder().text("0").maxLength(3).build(); // === 0. MAIN PANEL WINDOW ===
FTextField expansionField = new FTextField.Builder().text("0").maxLength(3).build(); // ===================================================================
FTextField otherField = new FTextField.Builder().text("0").maxLength(3).build(); // Initialise UI
FTextField customField = new FTextField.Builder().text("0").maxLength(3).build(); 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")); JPanel optionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2"));
optionsPanel.setVisible(false);
optionsPanel.setOpaque(false); optionsPanel.setOpaque(false);
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, growx");
optionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectRandomSets")).fontSize(17).fontStyle(Font.BOLD).build(), "h 40!, span 2");
JPanel leftOptionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2")); // === 2. FORMAT OPTIONS PANEL ===
leftOptionsPanel.setOpaque(false); // This will include a button for each format and a NO-Format Radio Button (default)
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectNumber") + ":").fontSize(14).fontStyle(Font.BOLD).build(), " span 2"); JPanel formatOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center"));
formatOptionsPanel.setOpaque(false);
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblCore") + ":").build()); formatOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") + ":")
leftOptionsPanel.add(coreField, "w 40!"); .fontSize(14).fontStyle(Font.BOLD).build(), "span 1");
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblExpansion") + ":").build());
leftOptionsPanel.add(expansionField, "w 40!");
leftOptionsPanel.add(new FLabel.Builder().text("Other:").build());
leftOptionsPanel.add(otherField, "w 40!");
if (customSetsExist){
leftOptionsPanel.add(new FLabel.Builder().text("Custom Editions:").build());
leftOptionsPanel.add(customField, "w 40!");
}
JPanel rightOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center, wrap 2"));
rightOptionsPanel.setOpaque(false);
rightOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") +":").fontSize(14).fontStyle(Font.BOLD).build(), "span 2");
ButtonGroup formatButtonGroup = new ButtonGroup(); ButtonGroup formatButtonGroup = new ButtonGroup();
List<GameFormat> gameFormats = new ArrayList<>(); List<GameFormat> gameFormats = new ArrayList<>();
FModel.getFormats().getSanctionedList().forEach(gameFormats::add); FModel.getFormats().getSanctionedList().forEach(gameFormats::add);
Map<String, FRadioButton> formatButtonGroupMap = new HashMap<>();
gameFormats.forEach(item -> { gameFormats.forEach(item -> {
if (item.getName().equals("Legacy")) { FRadioButton button = new FRadioButton(item.getName());
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblLegacyOrVintage")); button.setActionCommand(item.getName());
button.setActionCommand(item.getName()); button.addActionListener(new ActionListener() {
formatButtonGroup.add(button); @Override
rightOptionsPanel.add(button); public void actionPerformed(ActionEvent e) {
} else if (!item.getName().equals("Vintage")) { /* Whenever a Format button will be pressed, the status of the UI will be
FRadioButton button = new FRadioButton(item.getName()); updated accordingly.
button.setActionCommand(item.getName()); In particular, for each format, the number of allowed editions will be retrieved.
formatButtonGroup.add(button); (EMPTY LIST in case of NO RESTRICTIONS).
rightOptionsPanel.add(button); */
} List<String> allowedSetCodes = item.getAllowedSetCodes();
}); /* A. NO RESTRICTIONS:
-------------------
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblModernCardFrame")); All the components will be enabled, namely:
button.setActionCommand("Modern Card Frame"); - all nodes in the checkbox tree;
formatButtonGroup.add(button); - all spinners are enabled and their maximum value updated accordingly from the Tree status
rightOptionsPanel.add(button); */
if (allowedSetCodes.size() == 0) {
FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction")); for (CardEdition ce : allCardEditions) {
noFormatSelectionButton.setActionCommand("No Format Restriction"); String code = ce.getCode();
formatButtonGroup.add(noFormatSelectionButton); FTreeNode node = checkBoxTree.getNodeByKey(code);
rightOptionsPanel.add(noFormatSelectionButton); if (node != null)
noFormatSelectionButton.setSelected(true); checkBoxTree.setNodeEnabledStatus(node, true);
}
optionsPanel.add(leftOptionsPanel, "w 33%:40%:78%"); for (CardEdition.Type editionType : allEditionTypes.keySet()) {
optionsPanel.add(rightOptionsPanel, "w 33%:60%:78%"); int numberOfEnabledEditions = allEditionTypes.get(editionType);
if (numberOfEnabledEditions == 0)
FButton randomSelectionButton = new FButton(Localizer.getInstance().getMessage("lblRandomizeSets")); // This component will remain disabled, no matter the format selected
randomSelectionButton.addActionListener(actionEvent -> { continue;
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
int numberOfCoreSets = Integer.parseInt(coreField.getText()); FLabel label = labelsEditionTypeMap.get(editionType);
int numberOfExpansionSets = Integer.parseInt(expansionField.getText()); spinner.setEnabled(true);
int numberOfOtherSets = Integer.parseInt(otherField.getText()); label.setEnabled(true);
int numberOfCustomeSets = 0; FTreeNode node = checkBoxTree.getNodeByKey(editionType);
if (customSetsExist) if (node != null){
numberOfCustomeSets = Integer.parseInt(customField.getText()); int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
int currentValue = (int) spinner.getValue();
for (FCheckBox coreSet : coreSets) { spinner.setValue(Math.min(currentValue, maxValue));
coreSet.setSelected(false); SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
} m.setMaximum(maxValue);
for (FCheckBox expansionSet : expansionSets) { } else {
expansionSet.setSelected(false); spinner.setValue(0);
} }
for (FCheckBox otherSet : otherSets) { }
otherSet.setSelected(false); return;
}
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);
} }
} /* B. FORMAT RESTRICTIONS:
} -----------------------
All components matching with **allowed** editions will be ENABLED.
List<CardEdition> filteredExpansionSets = new ArrayList<>(); This includes:
for (CardEdition edition : editions) { - nodes in the checkbox tree;
if (edition.getType() == CardEdition.Type.EXPANSION) { - spinners (along with their corresponding MAX values as returned from Tree status).
if (formatPredicate != null && formatPredicate.test(edition)) { All components matching with the **BLACK LIST** of editions will be DISABLED
filteredExpansionSets.add(edition); (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<>(); // == Update Checkbox Tree ==
for (CardEdition edition : editions) { for (String code : codesToDisable) {
if (edition.getType() != CardEdition.Type.CORE && edition.getType() != CardEdition.Type.EXPANSION) { FTreeNode node = checkBoxTree.getNodeByKey(code);
if (formatPredicate != null && formatPredicate.test(edition)) { if (node != null)
filteredOtherSets.add(edition); checkBoxTree.setNodeEnabledStatus(node, false);
} }
} for (String code : allowedSetCodes) {
} FTreeNode node = checkBoxTree.getNodeByKey(code);
if (node != null)
Collections.shuffle(filteredCoreSets); checkBoxTree.setNodeEnabledStatus(node, true);
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);
} }
} // == update spinners ==
Collections.shuffle(filteredCustomSets); for (CardEdition.Type editionType : typesToDisable) {
} FSpinner spinner = spinnersEditionTypeMap.get(editionType);
FLabel label = labelsEditionTypeMap.get(editionType);
for (int i = 0; i < numberOfCoreSets && i < filteredCoreSets.size(); i++) { spinner.setEnabled(false);
String name = TextUtil.concatWithSpace(filteredCoreSets.get(i).getName(), TextUtil.enclosedParen(filteredCoreSets.get(i).getCode())); spinner.setValue(0);
for (FCheckBox set : coreSets) { label.setEnabled(false);
if (set.getText().equals(name)) {
set.setSelected(true);
} }
} for (CardEdition.Type editionType : allowedTypes) {
} if (allEditionTypes.get(editionType) == 0)
continue;
for (int i = 0; i < numberOfExpansionSets && i < filteredExpansionSets.size(); i++) { FLabel label = labelsEditionTypeMap.get(editionType);
String name = TextUtil.concatWithSpace(filteredExpansionSets.get(i).getName(), TextUtil.enclosedParen(filteredExpansionSets.get(i).getCode())); label.setEnabled(true);
for (FCheckBox set : expansionSets) { FSpinner spinner = spinnersEditionTypeMap.get(editionType);
if (set.getText().equals(name)) { spinner.setEnabled(true);
set.setSelected(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));
for (int i = 0; i < numberOfOtherSets && i < filteredOtherSets.size(); i++) { SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
String name = TextUtil.concatWithSpace(filteredOtherSets.get(i).getName(), TextUtil.enclosedParen(filteredOtherSets.get(i).getCode())); m.setMaximum(maxValue);
for (FCheckBox set : otherSets) { } else {
if (set.getText().equals(name)) { spinner.setValue(0);
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);
} }
} }
} }
} });
formatButtonGroup.add(button);
panel.repaintSelf(); formatOptionsPanel.add(button);
formatButtonGroupMap.put(item.getName(), button);
}); });
FButton clearSelectionButton = new FButton(Localizer.getInstance().getMessage("lblClearSelection")); // NO FORMAT Button
clearSelectionButton.addActionListener(actionEvent -> { FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction"));
for (FCheckBox coreSet : coreSets) { noFormatSelectionButton.setActionCommand("No Format");
coreSet.setSelected(false); noFormatSelectionButton.addActionListener(new ActionListener() {
} @Override
for (FCheckBox expansionSet : expansionSets) { public void actionPerformed(ActionEvent e) {
expansionSet.setSelected(false); for (CardEdition ce: allCardEditions){
} String code = ce.getCode();
for (FCheckBox otherSet : otherSets) { FTreeNode node = checkBoxTree.getNodeByKey(code);
otherSet.setSelected(false); if (node != null)
} checkBoxTree.setNodeEnabledStatus(node, true);
if (customSetsExist){ }
for (FCheckBox cmSet : customSets) { for (CardEdition.Type editionType : allEditionTypes.keySet()) {
cmSet.setSelected(false); 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")); mainDialogPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets"))
hideOptionsButton.addActionListener(actionEvent -> { .fontSize(20).build(), "center, span, wrap, gaptop 10");
optionsPanel.setVisible(false); mainDialogPanel.add(editionSelectionPanel, "aligny top, w 50%, span 1");
showOptionsButton.setVisible(true); mainDialogPanel.add(randomSelectionPanel, "aligny top, w 50%, span 1");
}); mainDialogPanel.add(optionsPanel, "center, w 100, span 2");
JPanel buttonPanel = new JPanel(new MigLayout("h 50!, center, gap 10, insets 0, ay center"));
buttonPanel.setOpaque(false);
buttonPanel.add(randomSelectionButton, "w 175!, h 28!");
buttonPanel.add(clearSelectionButton, "w 175!, h 28!");
buttonPanel.add(hideOptionsButton, " w 175!, h 28!");
optionsPanel.add(buttonPanel, "span 2, growx");
if (showWantReprintsCheckbox) {
optionsPanel.add(cbWantReprints, "center, span, wrap");
}
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, growx");
panel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets")).fontSize(20).build(), "center, span, wrap, gaptop 10");
String constraints = "aligny top";
panel.add(makeCheckBoxList(coreSets,
Localizer.getInstance().getMessage("lblCoreSets"), true),
constraints);
panel.add(makeCheckBoxList(expansionSets,
Localizer.getInstance().getMessage("lblExpansions"), false),
constraints);
panel.add(makeCheckBoxList(otherSets,
Localizer.getInstance().getMessage("lblOtherSets"), false),
constraints);
if (customSetsExist){
panel.add(makeCheckBoxList(customSets,
Localizer.getInstance().getMessage("lblCustomSets"), false),
constraints);
}
panel.add(showOptionsButton, "center, w 230!, h 30!, gap 10 0 20 0, span 3, hidemode 3");
panel.add(optionsPanel, "center, w 100, span 3, growx, hidemode 3");
final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel(); final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center")); overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center"));
@@ -378,17 +469,46 @@ public class DialogChooseSets {
JPanel southPanel = new JPanel(new MigLayout("insets 10, gap 30, ax center")); JPanel southPanel = new JPanel(new MigLayout("insets 10, gap 30, ax center"));
southPanel.setOpaque(false); southPanel.setOpaque(false);
southPanel.add(btnOk, "center, w 200!, h 30!"); southPanel.add(btnOk, "center, w 250!, h 30!");
southPanel.add(btnCancel, "center, w 200!, h 30!"); southPanel.add(btnCancel, "center, w 250!, h 30!");
panel.add(southPanel, "dock south, gapBottom 10"); mainDialogPanel.add(southPanel, "dock south, gapBottom 10");
overlay.add(panel); overlay.add(mainDialogPanel);
panel.getRootPane().setDefaultButton(btnOk); mainDialogPanel.getRootPane().setDefaultButton(btnOk);
SOverlayUtils.showOverlay(); SOverlayUtils.showOverlay();
} }
private int getMainDialogWidth() {
int winWidth = Singletons.getView().getFrame().getSize().width;
System.out.println("Win Width " + winWidth);
int[] sizeBoundaries = new int[] {800, 1024, 1280, 2048};
return calculateRelativePanelDimension(winWidth, 90, sizeBoundaries);
}
// So far, not yet used, but left here just in case
private int getMainDialogHeight() {
int winHeight = Singletons.getView().getFrame().getSize().height;
System.out.println("Win Height " + winHeight);
int[] sizeBoundaries = new int[] {600, 720, 780, 1024};
return calculateRelativePanelDimension(winHeight, 40, sizeBoundaries);
}
private int calculateRelativePanelDimension(int winDim, int ratio, int[] sizeBoundaries){
int relativeWinDimension = winDim * ratio / 100;
if (winDim < sizeBoundaries[0])
return relativeWinDimension;
for (int i = 1; i < sizeBoundaries.length; i++){
int left = sizeBoundaries[i-1];
int right = sizeBoundaries[i];
if (winDim <= left || winDim > right)
continue;
return Math.min(right*90/100, relativeWinDimension);
}
return sizeBoundaries[sizeBoundaries.length - 1] * 90 / 100; // Max Size fixed
}
public void setOkCallback(Runnable onOk) { public void setOkCallback(Runnable onOk) {
okCallback = onOk; okCallback = onOk;
} }
@@ -401,46 +521,16 @@ public class DialogChooseSets {
return wantReprints; return wantReprints;
} }
public void setWantReprintsCB(boolean isSet) {
cbWantReprints.setSelected(isSet);
}
private JPanel makeCheckBoxList(List<FCheckBox> sets, String title, boolean focused) {
choices.addAll(sets);
final FCheckBoxList<FCheckBox> cbl = new FCheckBoxList<>(false);
cbl.setListData(sets.toArray(new FCheckBox[sets.size()]));
cbl.setVisibleRowCount(20);
if (focused) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
cbl.requestFocusInWindow();
}
});
}
JPanel pnl = new JPanel(new MigLayout("center, wrap"));
pnl.setOpaque(false);
pnl.add(new FLabel.Builder().text(title).build());
pnl.add(new FScrollPane(cbl, true));
return pnl;
}
private void handleOk() { private void handleOk() {
Object[] checkedValues = this.checkBoxTree.getCheckedValues(true);
for (FCheckBox box : choices) { for (Object data: checkedValues){
if (box.isSelected()) { CardEdition edition = (CardEdition) data;
selectedSets.add(box.getName()); selectedSets.add(edition.getCode());
}
wantReprints = cbWantReprints.isSelected();
} }
wantReprints = cbWantReprints.isSelected();
if (null != okCallback) { if (null != okCallback) {
okCallback.run(); okCallback.run();
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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