mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
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:
@@ -1,20 +1,12 @@
|
||||
package forge;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.card.CardDb;
|
||||
import forge.card.*;
|
||||
import forge.card.CardDb.CardRequest;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.PrintSheet;
|
||||
import forge.item.BoosterBox;
|
||||
import forge.item.FatPack;
|
||||
import forge.item.PaperCard;
|
||||
@@ -75,7 +67,7 @@ public class StaticData {
|
||||
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
||||
this.blockDataFolder = blockDataFolder;
|
||||
this.customCardReader = customCardReader;
|
||||
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
|
||||
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder), true));
|
||||
this.prefferedArt = prefferedArt;
|
||||
lastInstance = this;
|
||||
List<String> funnyCards = new ArrayList<>();
|
||||
@@ -174,6 +166,22 @@ public class StaticData {
|
||||
return sortedEditions;
|
||||
}
|
||||
|
||||
private TreeMap<CardEdition.Type, List<CardEdition>> editionsTypeMap;
|
||||
public final Map<CardEdition.Type, List<CardEdition>> getEditionsTypeMap(){
|
||||
if (editionsTypeMap == null){
|
||||
editionsTypeMap = new TreeMap<>();
|
||||
for (CardEdition.Type editionType : CardEdition.Type.values()){
|
||||
editionsTypeMap.put(editionType, new ArrayList<>());
|
||||
}
|
||||
for (CardEdition edition : this.getSortedEditions()){
|
||||
CardEdition.Type key = edition.getType();
|
||||
List<CardEdition> editionsOfType = editionsTypeMap.get(key);
|
||||
editionsOfType.add(edition);
|
||||
}
|
||||
}
|
||||
return editionsTypeMap;
|
||||
}
|
||||
|
||||
public CardEdition getCardEdition(String setCode){
|
||||
CardEdition edition = this.editions.get(setCode);
|
||||
if (edition == null) // try custom editions
|
||||
|
||||
@@ -629,7 +629,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
return edition != null && edition.getType() != Type.PROMOS;
|
||||
return edition != null && edition.getType() != Type.PROMO;
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -641,7 +641,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
CardEdition edition = null;
|
||||
try {
|
||||
edition = editions.getEditionByCodeOrThrow(paperCard.getEdition());
|
||||
if (edition.getType() == Type.PROMOS||edition.getType() == Type.REPRINT)
|
||||
if (edition.getType() == Type.PROMO||edition.getType() == Type.REPRINT)
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
|
||||
@@ -37,6 +37,7 @@ import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import forge.util.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
@@ -52,11 +53,6 @@ import forge.card.CardDb.SetPreference;
|
||||
import forge.deck.CardPool;
|
||||
import forge.item.PaperCard;
|
||||
import forge.item.SealedProduct;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.FileSection;
|
||||
import forge.util.FileUtil;
|
||||
import forge.util.IItemReader;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.storage.StorageBase;
|
||||
import forge.util.storage.StorageReaderBase;
|
||||
import forge.util.storage.StorageReaderFolder;
|
||||
@@ -78,19 +74,23 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
CORE,
|
||||
EXPANSION,
|
||||
|
||||
REPRINT,
|
||||
ONLINE,
|
||||
STARTER,
|
||||
REPRINT,
|
||||
BOXED_SET,
|
||||
|
||||
DUEL_DECKS,
|
||||
PREMIUM_DECK_SERIES,
|
||||
FROM_THE_VAULT,
|
||||
COLLECTOR_EDITION,
|
||||
DUEL_DECK,
|
||||
PROMO,
|
||||
ONLINE,
|
||||
|
||||
OTHER,
|
||||
PROMOS,
|
||||
DRAFT,
|
||||
|
||||
COMMANDER,
|
||||
MULTIPLAYER,
|
||||
FUNNY,
|
||||
THIRDPARTY; // custom sets
|
||||
|
||||
OTHER, // FALLBACK CATEGORY
|
||||
CUSTOM_SET; // custom sets
|
||||
|
||||
public String getBoosterBoxDefault() {
|
||||
switch (this) {
|
||||
@@ -101,6 +101,19 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
String[] names = TextUtil.splitWithParenthesis(this.name().toLowerCase(), '_');
|
||||
for (int i = 0; i < names.length; i++)
|
||||
names[i] = TextUtil.capitalize(names[i]);
|
||||
return TextUtil.join(Arrays.asList(names), " ");
|
||||
}
|
||||
|
||||
public static Type fromString(String label){
|
||||
List<String> names = Arrays.asList(TextUtil.splitWithParenthesis(label.toUpperCase(), ' '));
|
||||
String value = TextUtil.join(names, "_");
|
||||
return Type.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum FoilType {
|
||||
@@ -476,8 +489,16 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
}
|
||||
|
||||
public static class Reader extends StorageReaderFolder<CardEdition> {
|
||||
private boolean isCustomEditions;
|
||||
|
||||
public Reader(File path) {
|
||||
super(path, CardEdition.FN_GET_CODE);
|
||||
this.isCustomEditions = false;
|
||||
}
|
||||
|
||||
public Reader(File path, boolean isCustomEditions) {
|
||||
super(path, CardEdition.FN_GET_CODE);
|
||||
this.isCustomEditions = isCustomEditions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -596,15 +617,20 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
res.alias = section.get("alias");
|
||||
res.borderColor = BorderColor.valueOf(section.get("border", "Black").toUpperCase(Locale.ENGLISH));
|
||||
String type = section.get("type");
|
||||
Type enumType = Type.UNKNOWN;
|
||||
if (null != type && !type.isEmpty()) {
|
||||
try {
|
||||
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// ignore; type will get UNKNOWN
|
||||
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type);
|
||||
if (this.isCustomEditions){
|
||||
enumType = Type.CUSTOM_SET; // Forcing ThirdParty Edition Type to avoid inconsistencies
|
||||
} else {
|
||||
String type = section.get("type");
|
||||
if (null != type && !type.isEmpty()) {
|
||||
try {
|
||||
enumType = Type.valueOf(type.toUpperCase(Locale.ENGLISH));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// ignore; type will get UNKNOWN
|
||||
System.err.println("Ignoring unknown type in set definitions: name: " + res.name + "; type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
res.type = enumType;
|
||||
res.prerelease = section.get("Prerelease", null);
|
||||
|
||||
@@ -57,8 +57,28 @@ import forge.util.storage.StorageReaderRecursiveFolderWithUserFolder;
|
||||
|
||||
public class GameFormat implements Comparable<GameFormat> {
|
||||
private final String name;
|
||||
public enum FormatType {Sanctioned, Casual, Historic, Digital, Custom}
|
||||
public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Arena, Custom}
|
||||
public enum FormatType {
|
||||
SANCTIONED,
|
||||
CASUAL,
|
||||
HISTORIC,
|
||||
DIGITAL,
|
||||
CUSTOM
|
||||
}
|
||||
public enum FormatSubType {
|
||||
BLOCK,
|
||||
STANDARD,
|
||||
EXTENDED,
|
||||
PIONEER,
|
||||
MODERN,
|
||||
LEGACY,
|
||||
VINTAGE,
|
||||
COMMANDER,
|
||||
PLANECHASE,
|
||||
VIDEOGAME,
|
||||
MTGO,
|
||||
ARENA,
|
||||
CUSTOM
|
||||
}
|
||||
|
||||
// contains allowed sets, when empty allows all sets
|
||||
private FormatType formatType;
|
||||
@@ -85,11 +105,11 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
private final int index;
|
||||
|
||||
public GameFormat(final String fName, final Iterable<String> sets, final List<String> bannedCards) {
|
||||
this(fName, parseDate(DEFAULTDATE), sets, bannedCards, null, false, null, null, 0, FormatType.Custom, FormatSubType.Custom);
|
||||
this(fName, parseDate(DEFAULTDATE), sets, bannedCards, null, false, null, null, 0, FormatType.CUSTOM, FormatSubType.CUSTOM);
|
||||
}
|
||||
|
||||
public static final GameFormat NoFormat = new GameFormat("(none)", parseDate(DEFAULTDATE) , null, null, null, false
|
||||
, null, null, Integer.MAX_VALUE, FormatType.Custom, FormatSubType.Custom);
|
||||
, null, null, Integer.MAX_VALUE, FormatType.CUSTOM, FormatSubType.CUSTOM);
|
||||
|
||||
public GameFormat(final String fName, final Date effectiveDate, final Iterable<String> sets, final List<String> bannedCards,
|
||||
final List<String> restrictedCards, Boolean restrictedLegendary, final List<String> additionalCards,
|
||||
@@ -276,6 +296,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
if (null == other) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (other.formatType != formatType){
|
||||
return formatType.compareTo(other.formatType);
|
||||
}else{
|
||||
@@ -283,10 +304,11 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return formatSubType.compareTo(other.formatSubType);
|
||||
}
|
||||
}
|
||||
if (formatType.equals(FormatType.Historic)){
|
||||
if(effectiveDate!=other.effectiveDate) {//for matching dates or default dates default to name sorting
|
||||
return other.effectiveDate.compareTo(effectiveDate);
|
||||
}
|
||||
if (formatType.equals(FormatType.HISTORIC)){
|
||||
int compareDates = this.effectiveDate.compareTo(other.effectiveDate);
|
||||
if (compareDates != 0)
|
||||
return compareDates;
|
||||
return (this.index - other.index);
|
||||
}
|
||||
return name.compareTo(other.name);
|
||||
//return index - other.index;
|
||||
@@ -338,15 +360,15 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
String title = section.get("name");
|
||||
FormatType formatType;
|
||||
try {
|
||||
formatType = FormatType.valueOf(section.get("type"));
|
||||
formatType = FormatType.valueOf(section.get("type").toUpperCase());
|
||||
} catch (Exception e) {
|
||||
formatType = FormatType.Custom;
|
||||
formatType = FormatType.CUSTOM;
|
||||
}
|
||||
FormatSubType formatsubType;
|
||||
try {
|
||||
formatsubType = FormatSubType.valueOf(section.get("subtype"));
|
||||
formatsubType = FormatSubType.valueOf(section.get("subtype").toUpperCase());
|
||||
} catch (Exception e) {
|
||||
formatsubType = FormatSubType.Custom;
|
||||
formatsubType = FormatSubType.CUSTOM;
|
||||
}
|
||||
Integer idx = section.getInt("order");
|
||||
String dateStr = section.get("effective");
|
||||
@@ -432,7 +454,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public Iterable<GameFormat> getSanctionedList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for(GameFormat format: naturallyOrdered){
|
||||
if(format.getFormatType().equals(FormatType.Sanctioned)){
|
||||
if(format.getFormatType().equals(FormatType.SANCTIONED)){
|
||||
coreList.add(format);
|
||||
}
|
||||
}
|
||||
@@ -442,8 +464,8 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public Iterable<GameFormat> getFilterList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for(GameFormat format: naturallyOrdered){
|
||||
if(!format.getFormatType().equals(FormatType.Historic)
|
||||
&&!format.getFormatType().equals(FormatType.Digital)){
|
||||
if(!format.getFormatType().equals(FormatType.HISTORIC)
|
||||
&&!format.getFormatType().equals(FormatType.DIGITAL)){
|
||||
coreList.add(format);
|
||||
}
|
||||
}
|
||||
@@ -453,7 +475,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public Iterable<GameFormat> getHistoricList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for(GameFormat format: naturallyOrdered){
|
||||
if(format.getFormatType().equals(FormatType.Historic)){
|
||||
if(format.getFormatType().equals(FormatType.HISTORIC)){
|
||||
coreList.add(format);
|
||||
}
|
||||
}
|
||||
@@ -463,7 +485,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public Map<String, List<GameFormat>> getHistoricMap() {
|
||||
Map<String, List<GameFormat>> coreList = new HashMap<>();
|
||||
for(GameFormat format: naturallyOrdered){
|
||||
if(format.getFormatType().equals(FormatType.Historic)){
|
||||
if(format.getFormatType().equals(FormatType.HISTORIC)){
|
||||
String alpha = format.getName().substring(0,1);
|
||||
if(!coreList.containsKey(alpha)){
|
||||
coreList.put(alpha,new ArrayList<>());
|
||||
@@ -528,15 +550,15 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
Set<FormatSubType> coveredTypes = new HashSet<>();
|
||||
CardPool allCards = deck.getAllCardsInASinglePool();
|
||||
for(GameFormat gf : reverseDateOrdered) {
|
||||
if (gf.getFormatType().equals(FormatType.Digital) && !exhaustive){
|
||||
if (gf.getFormatType().equals(FormatType.DIGITAL) && !exhaustive){
|
||||
//exclude Digital formats from lists for now
|
||||
continue;
|
||||
}
|
||||
if (gf.getFormatSubType().equals(FormatSubType.Commander)){
|
||||
if (gf.getFormatSubType().equals(FormatSubType.COMMANDER)){
|
||||
//exclude Commander format as other deck checks are not performed here
|
||||
continue;
|
||||
}
|
||||
if (gf.getFormatType().equals(FormatType.Historic) && coveredTypes.contains(gf.getFormatSubType())
|
||||
if (gf.getFormatType().equals(FormatType.HISTORIC) && coveredTypes.contains(gf.getFormatSubType())
|
||||
&& !exhaustive){
|
||||
//exclude duplicate formats - only keep first of e.g. Standard historical
|
||||
continue;
|
||||
@@ -570,7 +592,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return gf1.formatSubType.compareTo(gf2.formatSubType);
|
||||
}
|
||||
}
|
||||
if (gf1.formatType.equals(FormatType.Historic)){
|
||||
if (gf1.formatType.equals(FormatType.HISTORIC)){
|
||||
if(gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting
|
||||
return gf1.effectiveDate.compareTo(gf2.effectiveDate);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.itemmanager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
@@ -11,21 +13,8 @@ import forge.gamemodes.quest.QuestWorld;
|
||||
import forge.gamemodes.quest.data.QuestPreferences;
|
||||
import forge.gui.GuiUtils;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.filters.AdvancedSearchFilter;
|
||||
import forge.itemmanager.filters.CardCMCFilter;
|
||||
import forge.itemmanager.filters.CardCMCRangeFilter;
|
||||
import forge.itemmanager.filters.CardColorFilter;
|
||||
import forge.itemmanager.filters.CardFoilFilter;
|
||||
import forge.itemmanager.filters.CardFormatFilter;
|
||||
import forge.itemmanager.filters.CardPowerFilter;
|
||||
import forge.itemmanager.filters.CardQuestWorldFilter;
|
||||
import forge.itemmanager.filters.CardRatingFilter;
|
||||
import forge.itemmanager.filters.CardSearchFilter;
|
||||
import forge.itemmanager.filters.CardSetFilter;
|
||||
import forge.itemmanager.filters.CardToughnessFilter;
|
||||
import forge.itemmanager.filters.CardTypeFilter;
|
||||
import forge.itemmanager.filters.FormatFilter;
|
||||
import forge.itemmanager.filters.ItemFilter;
|
||||
import forge.itemmanager.filters.*;
|
||||
import forge.localinstance.properties.ForgePreferences;
|
||||
import forge.model.FModel;
|
||||
import forge.screens.home.quest.DialogChooseFormats;
|
||||
import forge.screens.home.quest.DialogChooseSets;
|
||||
@@ -162,6 +151,28 @@ public class CardManager extends ItemManager<PaperCard> {
|
||||
}
|
||||
menu.add(world);
|
||||
|
||||
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.LOAD_HISTORIC_FORMATS)) {
|
||||
JMenu blocks = GuiUtils.createMenu(localizer.getMessage("lblBlock"));
|
||||
List<GameFormat> blockFormats = new ArrayList<>();
|
||||
for (GameFormat format : FModel.getFormats().getHistoricList()){
|
||||
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
|
||||
continue;
|
||||
if (!format.getName().endsWith("Block"))
|
||||
continue;
|
||||
blockFormats.add(format);
|
||||
}
|
||||
Collections.sort(blockFormats); // GameFormat will be sorted by Index!
|
||||
for (final GameFormat f : blockFormats) {
|
||||
GuiUtils.addMenuItem(blocks, f.getName(), null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardBlockFilter(itemManager, f));
|
||||
}
|
||||
}, CardBlockFilter.canAddCardBlockWorld(f, itemManager.getFilter(CardBlockFilter.class)));
|
||||
}
|
||||
menu.add(blocks);
|
||||
}
|
||||
|
||||
GuiUtils.addSeparator(menu);
|
||||
|
||||
GuiUtils.addMenuItem(menu, localizer.getMessage("lblColors"), null, new Runnable() {
|
||||
|
||||
@@ -4,9 +4,7 @@ import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
@@ -14,6 +12,8 @@ import javax.swing.JTable;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import forge.itemmanager.filters.*;
|
||||
import forge.localinstance.properties.ForgePreferences;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.Singletons;
|
||||
@@ -29,15 +29,6 @@ import forge.gui.GuiUtils;
|
||||
import forge.gui.UiCommand;
|
||||
import forge.gui.framework.FScreen;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.itemmanager.filters.AdvancedSearchFilter;
|
||||
import forge.itemmanager.filters.DeckColorFilter;
|
||||
import forge.itemmanager.filters.DeckFolderFilter;
|
||||
import forge.itemmanager.filters.DeckFormatFilter;
|
||||
import forge.itemmanager.filters.DeckQuestWorldFilter;
|
||||
import forge.itemmanager.filters.DeckSearchFilter;
|
||||
import forge.itemmanager.filters.DeckSetFilter;
|
||||
import forge.itemmanager.filters.FormatFilter;
|
||||
import forge.itemmanager.filters.ItemFilter;
|
||||
import forge.itemmanager.views.ItemCellRenderer;
|
||||
import forge.itemmanager.views.ItemListView;
|
||||
import forge.itemmanager.views.ItemTableColumn;
|
||||
@@ -123,7 +114,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
|
||||
/**
|
||||
* Sets the delete command.
|
||||
*
|
||||
* @param c0   {@link forge.forge.gui.UiCommand} command executed on delete.
|
||||
* @param c0   {@link forge.gui.UiCommand} command executed on delete.
|
||||
*/
|
||||
public void setDeleteCommand(final UiCommand c0) {
|
||||
this.cmdDelete = c0;
|
||||
@@ -132,7 +123,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
|
||||
/**
|
||||
* Sets the select command.
|
||||
*
|
||||
* @param c0   {@link forge.forge.gui.UiCommand} command executed on row select.
|
||||
* @param c0   {@link forge.gui.UiCommand} command executed on row select.
|
||||
*/
|
||||
public void setSelectCommand(final UiCommand c0) {
|
||||
this.cmdSelect = c0;
|
||||
@@ -289,6 +280,28 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
|
||||
}
|
||||
menu.add(world);
|
||||
|
||||
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.LOAD_HISTORIC_FORMATS)) {
|
||||
JMenu blocks = GuiUtils.createMenu(localizer.getMessage("lblBlock"));
|
||||
List<GameFormat> blockFormats = new ArrayList<>();
|
||||
for (GameFormat format : FModel.getFormats().getHistoricList()){
|
||||
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
|
||||
continue;
|
||||
if (!format.getName().endsWith("Block"))
|
||||
continue;
|
||||
blockFormats.add(format);
|
||||
}
|
||||
Collections.sort(blockFormats); // GameFormat will be sorted by Index!
|
||||
for (final GameFormat f : blockFormats) {
|
||||
GuiUtils.addMenuItem(blocks, f.getName(), null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addFilter(new DeckBlockFilter(DeckManager.this, f));
|
||||
}
|
||||
}, DeckBlockFilter.canAddCardBlock(f, getFilter(DeckBlockFilter.class)));
|
||||
}
|
||||
menu.add(blocks);
|
||||
}
|
||||
|
||||
GuiUtils.addSeparator(menu);
|
||||
|
||||
GuiUtils.addMenuItem(menu, localizer.getMessage("lblColors"), null, new Runnable() {
|
||||
|
||||
@@ -138,11 +138,7 @@ public abstract class ItemManager<T extends InventoryItem> extends JPanel implem
|
||||
protected boolean lockFiltering;
|
||||
|
||||
/**
|
||||
* ItemManager Constructor.
|
||||
*
|
||||
* @param genericType0 the class of item that this table will contain
|
||||
* @param statLabels0 stat labels for this item manager
|
||||
* @param wantUnique0 whether this table should display only one item with the same name
|
||||
* ItemManager Constructor
|
||||
*/
|
||||
protected ItemManager(final Class<T> genericType0, final CDetailPicture cDetailPicture, final boolean wantUnique0) {
|
||||
this.cDetailPicture = cDetailPicture;
|
||||
@@ -1043,9 +1039,7 @@ public abstract class ItemManager<T extends InventoryItem> extends JPanel implem
|
||||
|
||||
/**
|
||||
*
|
||||
* updateView.
|
||||
*
|
||||
* @param bForceFilter
|
||||
* updateView
|
||||
*/
|
||||
public void updateView(final boolean forceFilter, final Iterable<T> itemsToSelect) {
|
||||
final boolean useFilter = (forceFilter && (this.filterPredicate != null)) || !isUnfiltered();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,21 @@ public class CardQuestWorldFilter extends CardFormatFilter {
|
||||
private final Set<QuestWorld> questWorlds = new HashSet<>();
|
||||
|
||||
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0) {
|
||||
this(itemManager0, true);
|
||||
}
|
||||
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, boolean allowReprints) {
|
||||
super(itemManager0);
|
||||
this.allowReprints = allowReprints;
|
||||
}
|
||||
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, final QuestWorld questWorld0) {
|
||||
this(itemManager0, questWorld0, true);
|
||||
}
|
||||
|
||||
public CardQuestWorldFilter(final ItemManager<? super PaperCard> itemManager0, final QuestWorld questWorld0, boolean allowReprints){
|
||||
super(itemManager0);
|
||||
this.questWorlds.add(questWorld0);
|
||||
this.formats.add(getQuestWorldFormat(questWorld0));
|
||||
this.allowReprints = allowReprints;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,7 @@ import forge.screens.home.quest.DialogChooseSets;
|
||||
*
|
||||
*/
|
||||
public class CardSetFilter extends CardFormatFilter {
|
||||
private final Set<String> sets = new HashSet<>();
|
||||
protected final Set<String> sets = new HashSet<>();
|
||||
|
||||
public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, boolean allowReprints0) {
|
||||
super(itemManager0);
|
||||
@@ -50,9 +50,9 @@ public class CardSetFilter extends CardFormatFilter {
|
||||
}
|
||||
|
||||
public void edit(final ItemManager<? super PaperCard> itemManager) {
|
||||
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true);
|
||||
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true,
|
||||
this.allowReprints);
|
||||
final CardSetFilter itemFilter = this;
|
||||
dialog.setWantReprintsCB(allowReprints);
|
||||
|
||||
dialog.setOkCallback(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class DeckFormatFilter extends FormatFilter<DeckProxy> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<DeckProxy> buildPredicate() {
|
||||
protected Predicate<DeckProxy> buildPredicate() {
|
||||
return DeckProxy.createPredicate(SFilterUtil.buildFormatFilter(this.formats, this.allowReprints));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,16 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.game.GameFormat;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.screens.home.quest.DialogChooseSets;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
||||
|
||||
|
||||
public class DeckSetFilter extends DeckFormatFilter {
|
||||
private final Set<String> sets = new HashSet<>();
|
||||
protected final Set<String> sets = new HashSet<>();
|
||||
|
||||
public DeckSetFilter(ItemManager<? super DeckProxy> itemManager0, Collection<String> sets0, boolean allowReprints0) {
|
||||
super(itemManager0);
|
||||
@@ -47,7 +49,8 @@ public class DeckSetFilter extends DeckFormatFilter {
|
||||
}
|
||||
|
||||
public void edit() {
|
||||
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true);
|
||||
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true,
|
||||
this.allowReprints);
|
||||
dialog.setOkCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -74,4 +77,14 @@ public class DeckSetFilter extends DeckFormatFilter {
|
||||
protected Iterable<String> getList() {
|
||||
return this.sets;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<DeckProxy> buildPredicate() {
|
||||
return new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(@NullableDecl DeckProxy input) {
|
||||
return input != null && sets.contains(input.getEdition().getCode());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +48,15 @@ public class DialogChooseFormats {
|
||||
FCheckBox box = new FCheckBox(format.getName());
|
||||
box.setName(format.getName());
|
||||
switch (format.getFormatType()){
|
||||
case Sanctioned:
|
||||
case SANCTIONED:
|
||||
sanctioned.add(box);
|
||||
break;
|
||||
case Historic:
|
||||
case HISTORIC:
|
||||
historic.add(box);
|
||||
break;
|
||||
case Custom:
|
||||
case Casual:
|
||||
case Digital:
|
||||
case CUSTOM:
|
||||
case CASUAL:
|
||||
case DIGITAL:
|
||||
default:
|
||||
casual.add(box);
|
||||
break;
|
||||
|
||||
@@ -1,40 +1,25 @@
|
||||
package forge.screens.home.quest;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.swing.ButtonGroup;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.Singletons;
|
||||
import forge.toolbox.*;
|
||||
import forge.card.CardEdition;
|
||||
import forge.game.GameFormat;
|
||||
import forge.gui.SOverlayUtils;
|
||||
import forge.localinstance.skin.FSkinProp;
|
||||
import forge.model.FModel;
|
||||
import forge.toolbox.FButton;
|
||||
import forge.toolbox.FCheckBox;
|
||||
import forge.toolbox.FCheckBoxList;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FOverlay;
|
||||
import forge.toolbox.FPanel;
|
||||
import forge.toolbox.FRadioButton;
|
||||
import forge.toolbox.FScrollPane;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FTextField;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import forge.toolbox.FCheckBoxTree.FTreeNode;
|
||||
import forge.toolbox.FCheckBoxTree.FTreeNodeData;
|
||||
|
||||
public class DialogChooseSets {
|
||||
|
||||
@@ -42,312 +27,418 @@ public class DialogChooseSets {
|
||||
private boolean wantReprints = true;
|
||||
private Runnable okCallback;
|
||||
|
||||
private final List<FCheckBox> choices = new ArrayList<>();
|
||||
private final FCheckBox cbWantReprints = new FCheckBox(Localizer.getInstance().getMessage("lblDisplayRecentSetReprints"));
|
||||
private final FCheckBoxTree checkBoxTree = new FCheckBoxTree();
|
||||
|
||||
// lists are of set codes (e.g. "2ED")
|
||||
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets, boolean showWantReprintsCheckbox) {
|
||||
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
|
||||
boolean showWantReprintsCheckbox) {
|
||||
this(preselectedSets, unselectableSets, showWantReprintsCheckbox, false);
|
||||
}
|
||||
|
||||
// create a local copy of the editions list so we can sort it
|
||||
List<CardEdition> editions = Lists.newArrayList(FModel.getMagicDb().getEditions());
|
||||
Collections.sort(editions);
|
||||
Collections.reverse(editions);
|
||||
public DialogChooseSets(Collection<String> preselectedSets, Collection<String> unselectableSets,
|
||||
boolean showWantReprintsCheckbox, boolean allowReprints) {
|
||||
|
||||
List<CardEdition> customEditions = Lists.newArrayList(FModel.getMagicDb().getCustomEditions());
|
||||
boolean customSetsExist = (customEditions.size() > 0);
|
||||
if (customSetsExist){
|
||||
Collections.sort(customEditions);
|
||||
Collections.reverse(customEditions);
|
||||
}
|
||||
List<FCheckBox> coreSets = new ArrayList<>();
|
||||
List<FCheckBox> expansionSets = new ArrayList<>();
|
||||
List<FCheckBox> otherSets = new ArrayList<>();
|
||||
// get the map of each editions per type
|
||||
Map<CardEdition.Type, List<CardEdition>> editionsTypeMap = FModel.getMagicDb().getEditionsTypeMap();
|
||||
|
||||
for (CardEdition ce : editions) {
|
||||
String code = ce.getCode();
|
||||
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code)));
|
||||
box.setName(code);
|
||||
box.setSelected(null != preselectedSets && preselectedSets.contains(code));
|
||||
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code));
|
||||
switch (ce.getType()) {
|
||||
case CORE:
|
||||
coreSets.add(box);
|
||||
break;
|
||||
case EXPANSION:
|
||||
expansionSets.add(box);
|
||||
break;
|
||||
default:
|
||||
otherSets.add(box);
|
||||
break;
|
||||
/* Gather all the different types among preselected and unselectable sets (if any)
|
||||
lists are of set codes (e.g. "2ED", edition.getCode())
|
||||
|
||||
NOTE: preselected SetTypes will be created as TreeSet as the ordering of Types will be used to
|
||||
decide which type in the UI list of types should be selected, in case of multiple types
|
||||
available in preselected types (e.g. expansion and core, core will be selected as it comes first) */
|
||||
Set<CardEdition.Type> preselectedTypes = null;
|
||||
if (preselectedSets != null){
|
||||
preselectedTypes = new TreeSet<>();
|
||||
for (String code: preselectedSets){
|
||||
CardEdition edition = FModel.getMagicDb().getCardEdition(code);
|
||||
preselectedTypes.add(edition.getType());
|
||||
}
|
||||
}
|
||||
// CustomSet
|
||||
List<FCheckBox> customSets = new ArrayList<>();
|
||||
if (customSetsExist) {
|
||||
for (CardEdition ce : customEditions){
|
||||
|
||||
TreeMap<FTreeNodeData, List<FTreeNodeData>> editionTypeTreeData = new TreeMap<>();
|
||||
TreeMap<CardEdition.Type, Integer> allEditionTypes = new TreeMap<>();
|
||||
List<CardEdition> allCardEditions = new ArrayList<>();
|
||||
for (CardEdition.Type editionType : editionsTypeMap.keySet()) {
|
||||
List<CardEdition> editionsOfType = editionsTypeMap.get(editionType);
|
||||
if (editionsOfType.size() == 0) // skip empty set types
|
||||
continue;
|
||||
List<FTreeNodeData> editionPerTypeNodes = new ArrayList<>();
|
||||
allCardEditions.addAll(editionsOfType);
|
||||
int enabledEditionsOfTypeCounter = 0;
|
||||
for (CardEdition ce: editionsOfType){
|
||||
String code = ce.getCode();
|
||||
FCheckBox box = new FCheckBox(TextUtil.concatWithSpace(ce.getName(), TextUtil.enclosedParen(code)));
|
||||
box.setName(code);
|
||||
box.setSelected(null != preselectedSets && preselectedSets.contains(code));
|
||||
box.setEnabled(null == unselectableSets || !unselectableSets.contains(code));
|
||||
customSets.add(box);
|
||||
boolean isSelected = null != preselectedSets && preselectedSets.contains(code);
|
||||
boolean isEnabled = null == unselectableSets || !unselectableSets.contains(code);
|
||||
FTreeNodeData editionNode = new FTreeNodeData(ce, ce.getName(), ce.getCode());
|
||||
editionNode.isEnabled = isEnabled;
|
||||
editionNode.isSelected = isSelected;
|
||||
if (isEnabled)
|
||||
enabledEditionsOfTypeCounter += 1;
|
||||
editionPerTypeNodes.add(editionNode);
|
||||
}
|
||||
|
||||
editionTypeTreeData.put(new FTreeNodeData(editionType), editionPerTypeNodes);
|
||||
allEditionTypes.put(editionType, enabledEditionsOfTypeCounter);
|
||||
}
|
||||
int wrapCol = !customSetsExist ? 3 : 4;
|
||||
FPanel panel = new FPanel(new MigLayout(String.format("insets 10, gap 5, center, wrap %d", wrapCol)));
|
||||
panel.setOpaque(false);
|
||||
panel.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
|
||||
this.checkBoxTree.setTreeData(editionTypeTreeData);
|
||||
|
||||
FTextField coreField = new FTextField.Builder().text("0").maxLength(3).build();
|
||||
FTextField expansionField = new FTextField.Builder().text("0").maxLength(3).build();
|
||||
FTextField otherField = new FTextField.Builder().text("0").maxLength(3).build();
|
||||
FTextField customField = new FTextField.Builder().text("0").maxLength(3).build();
|
||||
// === 0. MAIN PANEL WINDOW ===
|
||||
// ===================================================================
|
||||
// Initialise UI
|
||||
FPanel mainDialogPanel = new FPanel(new MigLayout(
|
||||
String.format("insets 10, gap 5, center, wrap 2, w %d!", getMainDialogWidth())));
|
||||
mainDialogPanel.setOpaque(false);
|
||||
mainDialogPanel.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
|
||||
|
||||
// === 1. RANDOM SELECTION PANEL ===
|
||||
// ===================================================================
|
||||
JPanel randomSelectionPanel = new JPanel(new MigLayout("insets 10, gap 5, right, wrap 2"));
|
||||
randomSelectionPanel.setOpaque(false);
|
||||
|
||||
// === 2. RANDOM OPTIONS PANEL ===
|
||||
// Setup components for the random selection panel.
|
||||
// NOTES: These components need to be defined first, as they will also be controlled by
|
||||
// format selection buttons (enabled/disabled accordingly).
|
||||
randomSelectionPanel.add(new FLabel.Builder().text(
|
||||
Localizer.getInstance().getMessage("lblSelectRandomSets")).fontSize(14)
|
||||
.fontStyle(Font.BOLD).build(), "h 40!, w 100%, center, span 2");
|
||||
FButton randomSelectionButton = new FButton(Localizer.getInstance().getMessage("lblRandomizeSets"));
|
||||
randomSelectionButton.setFont(FSkin.getBoldFont(13));
|
||||
randomSelectionButton.setEnabled(false); // by default is not enabled
|
||||
|
||||
// === SPINNER AND LABELS ===
|
||||
TreeMap<CardEdition.Type, FSpinner> spinnersEditionTypeMap = new TreeMap<>();
|
||||
TreeMap<CardEdition.Type, FLabel> labelsEditionTypeMap = new TreeMap<>();
|
||||
List<FSpinner> editionTypeSpinners = new ArrayList<>();
|
||||
for (CardEdition.Type editionType: allEditionTypes.keySet()) {
|
||||
int enabledEditionCount = allEditionTypes.get(editionType);
|
||||
|
||||
FSpinner spinner = new FSpinner.Builder().initialValue(0).minValue(0).maxValue(enabledEditionCount).build();
|
||||
String labTxt = "<html>" + editionType.toString().replaceAll(" ", "<br>") + ": </html>";
|
||||
FLabel label = new FLabel.Builder().text(labTxt).fontSize(13).build();
|
||||
|
||||
// Determine status of component
|
||||
if (enabledEditionCount == 0) {
|
||||
// No editions enabled meaning:
|
||||
// the edition type HAS extensions but none of them is enabled!
|
||||
spinner.setEnabled(false);
|
||||
label.setEnabled(false);
|
||||
}
|
||||
editionTypeSpinners.add(spinner);
|
||||
labelsEditionTypeMap.put(editionType, label);
|
||||
spinnersEditionTypeMap.put(editionType, spinner);
|
||||
}
|
||||
// == SPINNERS ACTION PERFORMED ==
|
||||
editionTypeSpinners.forEach(spinner -> {
|
||||
spinner.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
// As soon as the value of a spinner becomes different from zero,
|
||||
// enabled the random selection button.
|
||||
int spinValue = (int) spinner.getValue();
|
||||
if (spinValue > 0) {
|
||||
if (!randomSelectionButton.isEnabled())
|
||||
randomSelectionButton.setEnabled(true);
|
||||
} else {
|
||||
// Similarly, when all spinners are set to zero,
|
||||
// disable the random selection button
|
||||
boolean allZeros = true;
|
||||
for (FSpinner spin : editionTypeSpinners) {
|
||||
int value = (int) spin.getValue();
|
||||
if (value != 0) {
|
||||
allZeros = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allZeros)
|
||||
randomSelectionButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// == ADD SPINNERS AND LABELS TO THE PANEL ==
|
||||
JPanel typeFieldsPanel = null;
|
||||
randomSelectionPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, center");
|
||||
randomSelectionPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("nlSelectRandomSets"))
|
||||
.fontSize(12).fontStyle(Font.ITALIC).build(), "w 80%!, h 22px!, gap 5 0 0 0, span 2, center");
|
||||
String pairPanelLayout = "wrap 2, w 30%";
|
||||
int componentIndex = 0;
|
||||
int pairPerPanel = 3;
|
||||
int panelCompsCount = 0;
|
||||
for (CardEdition.Type editionType : allEditionTypes.keySet()) {
|
||||
if (panelCompsCount == 0)
|
||||
typeFieldsPanel = new JPanel(new MigLayout("insets 5, wrap 3"));
|
||||
typeFieldsPanel.setOpaque(false);
|
||||
JPanel pairPanel = new JPanel(new MigLayout(pairPanelLayout));
|
||||
pairPanel.setOpaque(false);
|
||||
pairPanel.add(labelsEditionTypeMap.get(editionType), "w 100!, align left, span 1");
|
||||
pairPanel.add(spinnersEditionTypeMap.get(editionType), "w 45!, align right, span 1");
|
||||
typeFieldsPanel.add(pairPanel, "span 1, center, growx, h 50!");
|
||||
panelCompsCount += 1;
|
||||
componentIndex += 1;
|
||||
if ((panelCompsCount == pairPerPanel) || (componentIndex == editionTypeSpinners.size())) {
|
||||
// add panel to outer container if we ran out of space, or we are processing the last item
|
||||
randomSelectionPanel.add(typeFieldsPanel, "w 100%, span 2");
|
||||
panelCompsCount = 0; // reset counter for the new panel, in case
|
||||
}
|
||||
}
|
||||
randomSelectionPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, gap 0");
|
||||
FButton clearSelectionButton = new FButton(Localizer.getInstance().getMessage("lblClearSelection"));
|
||||
clearSelectionButton.setFont(FSkin.getBoldFont(13));
|
||||
|
||||
// == UPDATE RANDOM PANEL LAYOUT ==
|
||||
randomSelectionPanel.add(clearSelectionButton, "gaptop 15, w 40%, h 26!, center");
|
||||
randomSelectionPanel.add(randomSelectionButton, "gaptop 15, w 40%, h 26!, center");
|
||||
if (showWantReprintsCheckbox) {
|
||||
cbWantReprints.setSelected(allowReprints);
|
||||
randomSelectionPanel.add(cbWantReprints, "gaptop 10, left, span, wrap");
|
||||
}
|
||||
|
||||
// === 2. OPTIONS PANEL ===
|
||||
// ===================================================================
|
||||
JPanel optionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2"));
|
||||
optionsPanel.setVisible(false);
|
||||
optionsPanel.setOpaque(false);
|
||||
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, growx");
|
||||
optionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectRandomSets")).fontSize(17).fontStyle(Font.BOLD).build(), "h 40!, span 2");
|
||||
|
||||
JPanel leftOptionsPanel = new JPanel(new MigLayout("insets 10, gap 5, center, wrap 2"));
|
||||
leftOptionsPanel.setOpaque(false);
|
||||
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSelectNumber") + ":").fontSize(14).fontStyle(Font.BOLD).build(), " span 2");
|
||||
|
||||
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblCore") + ":").build());
|
||||
leftOptionsPanel.add(coreField, "w 40!");
|
||||
|
||||
leftOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblExpansion") + ":").build());
|
||||
leftOptionsPanel.add(expansionField, "w 40!");
|
||||
|
||||
leftOptionsPanel.add(new FLabel.Builder().text("Other:").build());
|
||||
leftOptionsPanel.add(otherField, "w 40!");
|
||||
|
||||
if (customSetsExist){
|
||||
leftOptionsPanel.add(new FLabel.Builder().text("Custom Editions:").build());
|
||||
leftOptionsPanel.add(customField, "w 40!");
|
||||
}
|
||||
|
||||
JPanel rightOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center, wrap 2"));
|
||||
rightOptionsPanel.setOpaque(false);
|
||||
rightOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") +":").fontSize(14).fontStyle(Font.BOLD).build(), "span 2");
|
||||
// === 2. FORMAT OPTIONS PANEL ===
|
||||
// This will include a button for each format and a NO-Format Radio Button (default)
|
||||
JPanel formatOptionsPanel = new JPanel(new MigLayout("insets 10, gap 25 5, center"));
|
||||
formatOptionsPanel.setOpaque(false);
|
||||
formatOptionsPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblFormatRestrictions") + ":")
|
||||
.fontSize(14).fontStyle(Font.BOLD).build(), "span 1");
|
||||
|
||||
ButtonGroup formatButtonGroup = new ButtonGroup();
|
||||
List<GameFormat> gameFormats = new ArrayList<>();
|
||||
FModel.getFormats().getSanctionedList().forEach(gameFormats::add);
|
||||
|
||||
Map<String, FRadioButton> formatButtonGroupMap = new HashMap<>();
|
||||
gameFormats.forEach(item -> {
|
||||
if (item.getName().equals("Legacy")) {
|
||||
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblLegacyOrVintage"));
|
||||
button.setActionCommand(item.getName());
|
||||
formatButtonGroup.add(button);
|
||||
rightOptionsPanel.add(button);
|
||||
} else if (!item.getName().equals("Vintage")) {
|
||||
FRadioButton button = new FRadioButton(item.getName());
|
||||
button.setActionCommand(item.getName());
|
||||
formatButtonGroup.add(button);
|
||||
rightOptionsPanel.add(button);
|
||||
}
|
||||
});
|
||||
|
||||
FRadioButton button = new FRadioButton(Localizer.getInstance().getMessage("lblModernCardFrame"));
|
||||
button.setActionCommand("Modern Card Frame");
|
||||
formatButtonGroup.add(button);
|
||||
rightOptionsPanel.add(button);
|
||||
|
||||
FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction"));
|
||||
noFormatSelectionButton.setActionCommand("No Format Restriction");
|
||||
formatButtonGroup.add(noFormatSelectionButton);
|
||||
rightOptionsPanel.add(noFormatSelectionButton);
|
||||
noFormatSelectionButton.setSelected(true);
|
||||
|
||||
optionsPanel.add(leftOptionsPanel, "w 33%:40%:78%");
|
||||
optionsPanel.add(rightOptionsPanel, "w 33%:60%:78%");
|
||||
|
||||
FButton randomSelectionButton = new FButton(Localizer.getInstance().getMessage("lblRandomizeSets"));
|
||||
randomSelectionButton.addActionListener(actionEvent -> {
|
||||
|
||||
int numberOfCoreSets = Integer.parseInt(coreField.getText());
|
||||
int numberOfExpansionSets = Integer.parseInt(expansionField.getText());
|
||||
int numberOfOtherSets = Integer.parseInt(otherField.getText());
|
||||
int numberOfCustomeSets = 0;
|
||||
if (customSetsExist)
|
||||
numberOfCustomeSets = Integer.parseInt(customField.getText());
|
||||
|
||||
for (FCheckBox coreSet : coreSets) {
|
||||
coreSet.setSelected(false);
|
||||
}
|
||||
for (FCheckBox expansionSet : expansionSets) {
|
||||
expansionSet.setSelected(false);
|
||||
}
|
||||
for (FCheckBox otherSet : otherSets) {
|
||||
otherSet.setSelected(false);
|
||||
}
|
||||
if (customSetsExist){
|
||||
for (FCheckBox customSet : customSets) {
|
||||
customSet.setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
Predicate<CardEdition> formatPredicate = null;
|
||||
for (GameFormat gameFormat : gameFormats) {
|
||||
if (gameFormat.getName().equals(formatButtonGroup.getSelection().getActionCommand())) {
|
||||
formatPredicate = edition -> gameFormat.editionLegalPredicate.apply(edition) && (unselectableSets == null || !unselectableSets.contains(edition.getCode()));
|
||||
} else if (formatButtonGroup.getSelection().getActionCommand().equals("Modern Card Frame")) {
|
||||
formatPredicate = edition -> edition.getDate().after(new Date(1059350399L * 1000L)) && (unselectableSets == null || !unselectableSets.contains(edition.getCode()));
|
||||
} else if (formatButtonGroup.getSelection().getActionCommand().equals("No Format Restriction")) {
|
||||
formatPredicate = edition -> unselectableSets == null || !unselectableSets.contains(edition.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
List<CardEdition> filteredCoreSets = new ArrayList<>();
|
||||
for (CardEdition edition : editions) {
|
||||
if (edition.getType() == CardEdition.Type.CORE) {
|
||||
if (formatPredicate != null && formatPredicate.test(edition)) {
|
||||
filteredCoreSets.add(edition);
|
||||
FRadioButton button = new FRadioButton(item.getName());
|
||||
button.setActionCommand(item.getName());
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
/* Whenever a Format button will be pressed, the status of the UI will be
|
||||
updated accordingly.
|
||||
In particular, for each format, the number of allowed editions will be retrieved.
|
||||
(EMPTY LIST in case of NO RESTRICTIONS).
|
||||
*/
|
||||
List<String> allowedSetCodes = item.getAllowedSetCodes();
|
||||
/* A. NO RESTRICTIONS:
|
||||
-------------------
|
||||
All the components will be enabled, namely:
|
||||
- all nodes in the checkbox tree;
|
||||
- all spinners are enabled and their maximum value updated accordingly from the Tree status
|
||||
*/
|
||||
if (allowedSetCodes.size() == 0) {
|
||||
for (CardEdition ce : allCardEditions) {
|
||||
String code = ce.getCode();
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(code);
|
||||
if (node != null)
|
||||
checkBoxTree.setNodeEnabledStatus(node, true);
|
||||
}
|
||||
for (CardEdition.Type editionType : allEditionTypes.keySet()) {
|
||||
int numberOfEnabledEditions = allEditionTypes.get(editionType);
|
||||
if (numberOfEnabledEditions == 0)
|
||||
// This component will remain disabled, no matter the format selected
|
||||
continue;
|
||||
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
|
||||
FLabel label = labelsEditionTypeMap.get(editionType);
|
||||
spinner.setEnabled(true);
|
||||
label.setEnabled(true);
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(editionType);
|
||||
if (node != null){
|
||||
int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
|
||||
int currentValue = (int) spinner.getValue();
|
||||
spinner.setValue(Math.min(currentValue, maxValue));
|
||||
SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
|
||||
m.setMaximum(maxValue);
|
||||
} else {
|
||||
spinner.setValue(0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<CardEdition> filteredExpansionSets = new ArrayList<>();
|
||||
for (CardEdition edition : editions) {
|
||||
if (edition.getType() == CardEdition.Type.EXPANSION) {
|
||||
if (formatPredicate != null && formatPredicate.test(edition)) {
|
||||
filteredExpansionSets.add(edition);
|
||||
/* B. FORMAT RESTRICTIONS:
|
||||
-----------------------
|
||||
All components matching with **allowed** editions will be ENABLED.
|
||||
This includes:
|
||||
- nodes in the checkbox tree;
|
||||
- spinners (along with their corresponding MAX values as returned from Tree status).
|
||||
All components matching with the **BLACK LIST** of editions will be DISABLED
|
||||
(Same as in the previous case).
|
||||
*/
|
||||
List<String> codesToDisable = new ArrayList<>();
|
||||
Set<CardEdition.Type> typesToDisable = new HashSet<>();
|
||||
Set<CardEdition.Type> allowedTypes = new HashSet<>();
|
||||
for (CardEdition ce : allCardEditions) {
|
||||
String code = ce.getCode();
|
||||
if (unselectableSets != null && unselectableSets.contains(code))
|
||||
continue;
|
||||
if (!allowedSetCodes.contains(code)) {
|
||||
codesToDisable.add(code);
|
||||
typesToDisable.add(ce.getType());
|
||||
} else {
|
||||
allowedTypes.add(ce.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: We need to distinguish CardEdition.Type not having any actual CardEdition
|
||||
// in the allowed sets (i.e. to be completely disabled) from those still
|
||||
// having partial sets to be allowed.
|
||||
// The latter will result in adjusted maxValues of the corresponding spinner,
|
||||
// as well as their current value, when necessary.
|
||||
typesToDisable.removeAll(allowedTypes);
|
||||
|
||||
List<CardEdition> filteredOtherSets = new ArrayList<>();
|
||||
for (CardEdition edition : editions) {
|
||||
if (edition.getType() != CardEdition.Type.CORE && edition.getType() != CardEdition.Type.EXPANSION) {
|
||||
if (formatPredicate != null && formatPredicate.test(edition)) {
|
||||
filteredOtherSets.add(edition);
|
||||
// == Update Checkbox Tree ==
|
||||
for (String code : codesToDisable) {
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(code);
|
||||
if (node != null)
|
||||
checkBoxTree.setNodeEnabledStatus(node, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.shuffle(filteredCoreSets);
|
||||
Collections.shuffle(filteredExpansionSets);
|
||||
Collections.shuffle(filteredOtherSets);
|
||||
|
||||
List<CardEdition> filteredCustomSets = new ArrayList<>();
|
||||
if (customSetsExist){
|
||||
for (CardEdition edition : customEditions) {
|
||||
if (formatPredicate != null && formatPredicate.test(edition)) {
|
||||
filteredCustomSets.add(edition);
|
||||
for (String code : allowedSetCodes) {
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(code);
|
||||
if (node != null)
|
||||
checkBoxTree.setNodeEnabledStatus(node, true);
|
||||
}
|
||||
}
|
||||
Collections.shuffle(filteredCustomSets);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberOfCoreSets && i < filteredCoreSets.size(); i++) {
|
||||
String name = TextUtil.concatWithSpace(filteredCoreSets.get(i).getName(), TextUtil.enclosedParen(filteredCoreSets.get(i).getCode()));
|
||||
for (FCheckBox set : coreSets) {
|
||||
if (set.getText().equals(name)) {
|
||||
set.setSelected(true);
|
||||
// == update spinners ==
|
||||
for (CardEdition.Type editionType : typesToDisable) {
|
||||
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
|
||||
FLabel label = labelsEditionTypeMap.get(editionType);
|
||||
spinner.setEnabled(false);
|
||||
spinner.setValue(0);
|
||||
label.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberOfExpansionSets && i < filteredExpansionSets.size(); i++) {
|
||||
String name = TextUtil.concatWithSpace(filteredExpansionSets.get(i).getName(), TextUtil.enclosedParen(filteredExpansionSets.get(i).getCode()));
|
||||
for (FCheckBox set : expansionSets) {
|
||||
if (set.getText().equals(name)) {
|
||||
set.setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberOfOtherSets && i < filteredOtherSets.size(); i++) {
|
||||
String name = TextUtil.concatWithSpace(filteredOtherSets.get(i).getName(), TextUtil.enclosedParen(filteredOtherSets.get(i).getCode()));
|
||||
for (FCheckBox set : otherSets) {
|
||||
if (set.getText().equals(name)) {
|
||||
set.setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (customSetsExist){
|
||||
for (int i = 0; i < numberOfCustomeSets && i < filteredCustomSets.size(); i++) {
|
||||
String name = TextUtil.concatWithSpace(filteredCustomSets.get(i).getName(),
|
||||
TextUtil.enclosedParen(filteredCustomSets.get(i).getCode()));
|
||||
for (FCheckBox set : customSets) {
|
||||
if (set.getText().equals(name)) {
|
||||
set.setSelected(true);
|
||||
for (CardEdition.Type editionType : allowedTypes) {
|
||||
if (allEditionTypes.get(editionType) == 0)
|
||||
continue;
|
||||
FLabel label = labelsEditionTypeMap.get(editionType);
|
||||
label.setEnabled(true);
|
||||
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
|
||||
spinner.setEnabled(true);
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(editionType);
|
||||
if (node != null){
|
||||
int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
|
||||
int currentValue = (int) spinner.getValue();
|
||||
spinner.setValue(Math.min(currentValue, maxValue));
|
||||
SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
|
||||
m.setMaximum(maxValue);
|
||||
} else {
|
||||
spinner.setValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panel.repaintSelf();
|
||||
|
||||
});
|
||||
formatButtonGroup.add(button);
|
||||
formatOptionsPanel.add(button);
|
||||
formatButtonGroupMap.put(item.getName(), button);
|
||||
});
|
||||
|
||||
FButton clearSelectionButton = new FButton(Localizer.getInstance().getMessage("lblClearSelection"));
|
||||
clearSelectionButton.addActionListener(actionEvent -> {
|
||||
for (FCheckBox coreSet : coreSets) {
|
||||
coreSet.setSelected(false);
|
||||
}
|
||||
for (FCheckBox expansionSet : expansionSets) {
|
||||
expansionSet.setSelected(false);
|
||||
}
|
||||
for (FCheckBox otherSet : otherSets) {
|
||||
otherSet.setSelected(false);
|
||||
}
|
||||
if (customSetsExist){
|
||||
for (FCheckBox cmSet : customSets) {
|
||||
cmSet.setSelected(false);
|
||||
// NO FORMAT Button
|
||||
FRadioButton noFormatSelectionButton = new FRadioButton(Localizer.getInstance().getMessage("lblNoFormatRestriction"));
|
||||
noFormatSelectionButton.setActionCommand("No Format");
|
||||
noFormatSelectionButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
for (CardEdition ce: allCardEditions){
|
||||
String code = ce.getCode();
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(code);
|
||||
if (node != null)
|
||||
checkBoxTree.setNodeEnabledStatus(node, true);
|
||||
}
|
||||
for (CardEdition.Type editionType : allEditionTypes.keySet()) {
|
||||
if (allEditionTypes.get(editionType) == 0)
|
||||
// This component will remain disabled, no matter the format selected
|
||||
continue;
|
||||
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
|
||||
FLabel label = labelsEditionTypeMap.get(editionType);
|
||||
spinner.setEnabled(true);
|
||||
label.setEnabled(true);
|
||||
FTreeNode node = checkBoxTree.getNodeByKey(editionType);
|
||||
if (node != null){
|
||||
int maxValue = checkBoxTree.getNumberOfActiveChildNodes(node);
|
||||
int currentValue = (int) spinner.getValue();
|
||||
spinner.setValue(Math.min(currentValue, maxValue));
|
||||
SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
|
||||
m.setMaximum(maxValue);
|
||||
} else {
|
||||
spinner.setValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
panel.repaintSelf();
|
||||
});
|
||||
formatButtonGroup.add(noFormatSelectionButton);
|
||||
formatOptionsPanel.add(noFormatSelectionButton);
|
||||
formatButtonGroupMap.put("No Format", noFormatSelectionButton);
|
||||
noFormatSelectionButton.setSelected(true);
|
||||
|
||||
// === Update Option Panel ===
|
||||
optionsPanel.add(formatOptionsPanel, "span 2, w 100%");
|
||||
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2");
|
||||
|
||||
// === EDITION (PER TYPE) SELECTION PANEL ===
|
||||
// Selected Editions Panel
|
||||
JPanel editionSelectionPanel = new JPanel(new MigLayout("insets 10, gap 25 5, wrap 1, align left"));
|
||||
editionSelectionPanel.setOpaque(false);
|
||||
editionSelectionPanel.add(new FLabel.Builder().text(
|
||||
Localizer.getInstance().getMessage("lblCardEditionTypeList")).fontSize(14)
|
||||
.fontStyle(Font.BOLD).build(), "h 40!, w 100%, center, span 1");
|
||||
this.checkBoxTree.setOpaque(false);
|
||||
FScrollPane selectionScroller = new FScrollPane(checkBoxTree, true);
|
||||
editionSelectionPanel.add(selectionScroller, "span 1, w 100%");
|
||||
|
||||
// ======== ADD ACTION LISTENERS TO CLEAR AND RANDOM SELECT BUTTONS
|
||||
clearSelectionButton.addActionListener(actionEvent -> {
|
||||
this.checkBoxTree.resetCheckingState();
|
||||
allEditionTypes.forEach((editionType, count) -> {
|
||||
if (count == 0)
|
||||
return;
|
||||
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
|
||||
FLabel label = labelsEditionTypeMap.get(editionType);
|
||||
spinner.setValue(0);
|
||||
spinner.setEnabled(true);
|
||||
label.setEnabled(true);
|
||||
});
|
||||
noFormatSelectionButton.setSelected(true);
|
||||
cbWantReprints.setSelected(false);
|
||||
mainDialogPanel.repaintSelf();
|
||||
});
|
||||
randomSelectionButton.addActionListener(actionEvent -> {
|
||||
Map<CardEdition.Type, Integer> countPerEditionType = new HashMap<>();
|
||||
for (CardEdition.Type editionType: allEditionTypes.keySet()){
|
||||
if (allEditionTypes.get(editionType) == 0)
|
||||
continue;
|
||||
FSpinner spinner = spinnersEditionTypeMap.get(editionType);
|
||||
if (!spinner.isEnabled())
|
||||
continue;
|
||||
int value = (int) spinner.getValue();
|
||||
if (value > 0)
|
||||
countPerEditionType.put(editionType, value);
|
||||
}
|
||||
// We can safely reset selections as this button would not be enabled at all
|
||||
// if at least one spinner has been modified, and so countPerEdition updated.
|
||||
checkBoxTree.resetCheckingState();
|
||||
String selectedFormat = formatButtonGroup.getSelection().getActionCommand();
|
||||
FRadioButton formatButton = formatButtonGroupMap.get(selectedFormat);
|
||||
formatButton.doClick();
|
||||
for (CardEdition.Type editionType : countPerEditionType.keySet()){
|
||||
int totalToSelect = countPerEditionType.get(editionType);
|
||||
FTreeNode setTypeNode = checkBoxTree.getNodeByKey(editionType);
|
||||
if (setTypeNode != null){
|
||||
List<FTreeNode> activeChildNodes = checkBoxTree.getActiveChildNodes(setTypeNode);
|
||||
Collections.shuffle(activeChildNodes);
|
||||
for (int i = 0; i < totalToSelect; i++)
|
||||
checkBoxTree.setNodeCheckStatus(activeChildNodes.get(i), true);
|
||||
}
|
||||
}
|
||||
mainDialogPanel.repaintSelf();
|
||||
});
|
||||
|
||||
FButton showOptionsButton = new FButton(Localizer.getInstance().getMessage("lblShowOptions"));
|
||||
showOptionsButton.addActionListener(actionEvent -> {
|
||||
optionsPanel.setVisible(true);
|
||||
showOptionsButton.setVisible(false);
|
||||
});
|
||||
// ===================================================================
|
||||
|
||||
FButton hideOptionsButton = new FButton(Localizer.getInstance().getMessage("lblHideOptions"));
|
||||
hideOptionsButton.addActionListener(actionEvent -> {
|
||||
optionsPanel.setVisible(false);
|
||||
showOptionsButton.setVisible(true);
|
||||
});
|
||||
|
||||
JPanel buttonPanel = new JPanel(new MigLayout("h 50!, center, gap 10, insets 0, ay center"));
|
||||
buttonPanel.setOpaque(false);
|
||||
buttonPanel.add(randomSelectionButton, "w 175!, h 28!");
|
||||
buttonPanel.add(clearSelectionButton, "w 175!, h 28!");
|
||||
buttonPanel.add(hideOptionsButton, " w 175!, h 28!");
|
||||
|
||||
optionsPanel.add(buttonPanel, "span 2, growx");
|
||||
|
||||
if (showWantReprintsCheckbox) {
|
||||
optionsPanel.add(cbWantReprints, "center, span, wrap");
|
||||
}
|
||||
|
||||
optionsPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "w 100%, span 2, growx");
|
||||
|
||||
panel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets")).fontSize(20).build(), "center, span, wrap, gaptop 10");
|
||||
|
||||
String constraints = "aligny top";
|
||||
panel.add(makeCheckBoxList(coreSets,
|
||||
Localizer.getInstance().getMessage("lblCoreSets"), true),
|
||||
constraints);
|
||||
panel.add(makeCheckBoxList(expansionSets,
|
||||
Localizer.getInstance().getMessage("lblExpansions"), false),
|
||||
constraints);
|
||||
panel.add(makeCheckBoxList(otherSets,
|
||||
Localizer.getInstance().getMessage("lblOtherSets"), false),
|
||||
constraints);
|
||||
if (customSetsExist){
|
||||
panel.add(makeCheckBoxList(customSets,
|
||||
Localizer.getInstance().getMessage("lblCustomSets"), false),
|
||||
constraints);
|
||||
}
|
||||
panel.add(showOptionsButton, "center, w 230!, h 30!, gap 10 0 20 0, span 3, hidemode 3");
|
||||
panel.add(optionsPanel, "center, w 100, span 3, growx, hidemode 3");
|
||||
mainDialogPanel.add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblChooseSets"))
|
||||
.fontSize(20).build(), "center, span, wrap, gaptop 10");
|
||||
mainDialogPanel.add(editionSelectionPanel, "aligny top, w 50%, span 1");
|
||||
mainDialogPanel.add(randomSelectionPanel, "aligny top, w 50%, span 1");
|
||||
mainDialogPanel.add(optionsPanel, "center, w 100, span 2");
|
||||
|
||||
final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
|
||||
overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center"));
|
||||
@@ -378,17 +469,46 @@ public class DialogChooseSets {
|
||||
|
||||
JPanel southPanel = new JPanel(new MigLayout("insets 10, gap 30, ax center"));
|
||||
southPanel.setOpaque(false);
|
||||
southPanel.add(btnOk, "center, w 200!, h 30!");
|
||||
southPanel.add(btnCancel, "center, w 200!, h 30!");
|
||||
southPanel.add(btnOk, "center, w 250!, h 30!");
|
||||
southPanel.add(btnCancel, "center, w 250!, h 30!");
|
||||
|
||||
panel.add(southPanel, "dock south, gapBottom 10");
|
||||
mainDialogPanel.add(southPanel, "dock south, gapBottom 10");
|
||||
|
||||
overlay.add(panel);
|
||||
panel.getRootPane().setDefaultButton(btnOk);
|
||||
overlay.add(mainDialogPanel);
|
||||
mainDialogPanel.getRootPane().setDefaultButton(btnOk);
|
||||
SOverlayUtils.showOverlay();
|
||||
|
||||
}
|
||||
|
||||
private int getMainDialogWidth() {
|
||||
int winWidth = Singletons.getView().getFrame().getSize().width;
|
||||
System.out.println("Win Width " + winWidth);
|
||||
int[] sizeBoundaries = new int[] {800, 1024, 1280, 2048};
|
||||
return calculateRelativePanelDimension(winWidth, 90, sizeBoundaries);
|
||||
}
|
||||
|
||||
// So far, not yet used, but left here just in case
|
||||
private int getMainDialogHeight() {
|
||||
int winHeight = Singletons.getView().getFrame().getSize().height;
|
||||
System.out.println("Win Height " + winHeight);
|
||||
int[] sizeBoundaries = new int[] {600, 720, 780, 1024};
|
||||
return calculateRelativePanelDimension(winHeight, 40, sizeBoundaries);
|
||||
}
|
||||
|
||||
private int calculateRelativePanelDimension(int winDim, int ratio, int[] sizeBoundaries){
|
||||
int relativeWinDimension = winDim * ratio / 100;
|
||||
if (winDim < sizeBoundaries[0])
|
||||
return relativeWinDimension;
|
||||
for (int i = 1; i < sizeBoundaries.length; i++){
|
||||
int left = sizeBoundaries[i-1];
|
||||
int right = sizeBoundaries[i];
|
||||
if (winDim <= left || winDim > right)
|
||||
continue;
|
||||
return Math.min(right*90/100, relativeWinDimension);
|
||||
}
|
||||
return sizeBoundaries[sizeBoundaries.length - 1] * 90 / 100; // Max Size fixed
|
||||
}
|
||||
|
||||
|
||||
public void setOkCallback(Runnable onOk) {
|
||||
okCallback = onOk;
|
||||
}
|
||||
@@ -400,47 +520,17 @@ public class DialogChooseSets {
|
||||
public boolean getWantReprints() {
|
||||
return wantReprints;
|
||||
}
|
||||
|
||||
public void setWantReprintsCB(boolean isSet) {
|
||||
cbWantReprints.setSelected(isSet);
|
||||
}
|
||||
|
||||
private JPanel makeCheckBoxList(List<FCheckBox> sets, String title, boolean focused) {
|
||||
choices.addAll(sets);
|
||||
final FCheckBoxList<FCheckBox> cbl = new FCheckBoxList<>(false);
|
||||
cbl.setListData(sets.toArray(new FCheckBox[sets.size()]));
|
||||
cbl.setVisibleRowCount(20);
|
||||
|
||||
if (focused) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cbl.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
JPanel pnl = new JPanel(new MigLayout("center, wrap"));
|
||||
pnl.setOpaque(false);
|
||||
pnl.add(new FLabel.Builder().text(title).build());
|
||||
pnl.add(new FScrollPane(cbl, true));
|
||||
return pnl;
|
||||
|
||||
}
|
||||
|
||||
private void handleOk() {
|
||||
|
||||
for (FCheckBox box : choices) {
|
||||
if (box.isSelected()) {
|
||||
selectedSets.add(box.getName());
|
||||
}
|
||||
wantReprints = cbWantReprints.isSelected();
|
||||
Object[] checkedValues = this.checkBoxTree.getCheckedValues(true);
|
||||
for (Object data: checkedValues){
|
||||
CardEdition edition = (CardEdition) data;
|
||||
selectedSets.add(edition.getCode());
|
||||
}
|
||||
wantReprints = cbWantReprints.isSelected();
|
||||
|
||||
if (null != okCallback) {
|
||||
okCallback.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
646
forge-gui-desktop/src/main/java/forge/toolbox/FCheckBoxTree.java
Normal file
646
forge-gui-desktop/src/main/java/forge/toolbox/FCheckBoxTree.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class FSkin {
|
||||
*
|
||||
* @param clr0 {@link java.awt.Color}
|
||||
* @param step int
|
||||
* @return {@link java.awt.CFaceolor}
|
||||
* @return {@link java.awt.Color}
|
||||
*/
|
||||
public static Color stepColor(final Color clr0, final int step) {
|
||||
int r = clr0.getRed();
|
||||
|
||||
@@ -136,56 +136,74 @@ public abstract class FormatFilter<T extends InventoryItem> extends ItemFilter<T
|
||||
|
||||
lstSets.addGroup("Core Sets");
|
||||
lstSets.addGroup("Expansions");
|
||||
lstSets.addGroup("Duel Decks");
|
||||
lstSets.addGroup("From the Vault");
|
||||
lstSets.addGroup("Premium Deck Series");
|
||||
lstSets.addGroup("Reprint Sets");
|
||||
lstSets.addGroup("Starter Sets");
|
||||
lstSets.addGroup("Reprint Sets");
|
||||
lstSets.addGroup("Boxed Sets");
|
||||
|
||||
lstSets.addGroup("Collector's Edition");
|
||||
lstSets.addGroup("Duel Decks");
|
||||
lstSets.addGroup("Promo Sets");
|
||||
lstSets.addGroup("Digital Sets");
|
||||
|
||||
lstSets.addGroup("Draft Innovation Sets");
|
||||
|
||||
|
||||
|
||||
lstSets.addGroup("Commander Sets");
|
||||
lstSets.addGroup("Multiplayer Sets");
|
||||
lstSets.addGroup("Other Supplemental Sets");
|
||||
lstSets.addGroup("Funny Sets");
|
||||
|
||||
lstSets.addGroup("Custom Sets");
|
||||
lstSets.addGroup("Other Sets");
|
||||
|
||||
List<CardEdition> sets = FModel.getMagicDb().getSortedEditions();
|
||||
for (CardEdition set : sets) {
|
||||
switch (set.getType()) {
|
||||
case CORE:
|
||||
lstSets.addItem(set, 0);
|
||||
break;
|
||||
case EXPANSION:
|
||||
lstSets.addItem(set, 1);
|
||||
break;
|
||||
case DUEL_DECKS:
|
||||
lstSets.addItem(set, 2);
|
||||
break;
|
||||
case FROM_THE_VAULT:
|
||||
lstSets.addItem(set, 3);
|
||||
break;
|
||||
case PREMIUM_DECK_SERIES:
|
||||
lstSets.addItem(set, 4);
|
||||
break;
|
||||
case REPRINT:
|
||||
lstSets.addItem(set, 5);
|
||||
break;
|
||||
case STARTER:
|
||||
lstSets.addItem(set, 6);
|
||||
break;
|
||||
case PROMOS:
|
||||
lstSets.addItem(set, 7);
|
||||
break;
|
||||
case ONLINE:
|
||||
lstSets.addItem(set, 8);
|
||||
break;
|
||||
case FUNNY:
|
||||
lstSets.addItem(set, 9);
|
||||
break;
|
||||
case THIRDPARTY:
|
||||
lstSets.addItem(set, 10);
|
||||
break;
|
||||
default:
|
||||
lstSets.addItem(set, 11);
|
||||
break;
|
||||
case CORE:
|
||||
lstSets.addItem(set, 0);
|
||||
break;
|
||||
case EXPANSION:
|
||||
lstSets.addItem(set, 1);
|
||||
break;
|
||||
case STARTER:
|
||||
lstSets.addItem(set, 2);
|
||||
break;
|
||||
case REPRINT:
|
||||
lstSets.addItem(set, 3);
|
||||
break;
|
||||
case BOXED_SET:
|
||||
lstSets.addItem(set,4);
|
||||
break;
|
||||
case COLLECTOR_EDITION:
|
||||
lstSets.addItem(set, 5);
|
||||
break;
|
||||
case DUEL_DECK:
|
||||
lstSets.addItem(set, 6);
|
||||
break;
|
||||
case PROMO:
|
||||
lstSets.addItem(set, 7);
|
||||
break;
|
||||
case ONLINE:
|
||||
lstSets.addItem(set, 8);
|
||||
break;
|
||||
case DRAFT:
|
||||
lstSets.addItem(set, 9);
|
||||
break;
|
||||
case COMMANDER:
|
||||
lstSets.addItem(set, 10);
|
||||
break;
|
||||
case MULTIPLAYER:
|
||||
lstSets.addItem(set, 11);
|
||||
break;
|
||||
case OTHER:
|
||||
lstSets.addItem(set, 12);
|
||||
break;
|
||||
case FUNNY:
|
||||
lstSets.addItem(set, 13);
|
||||
break;
|
||||
default: // THIRDPARTY - Custom Sets
|
||||
lstSets.addItem(set, 14);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,16 +27,16 @@ public class HistoricFormatSelect extends FScreen {
|
||||
|
||||
private GameFormat selectedFormat;
|
||||
private final FGroupList<GameFormat> lstFormats = add(new FGroupList<>());
|
||||
private final Set<GameFormat.FormatSubType> historicSubTypes = new HashSet<>(Arrays.asList(GameFormat.FormatSubType.Block,
|
||||
GameFormat.FormatSubType.Standard,GameFormat.FormatSubType.Extended,GameFormat.FormatSubType.Modern,
|
||||
GameFormat.FormatSubType.Legacy, GameFormat.FormatSubType.Vintage));
|
||||
private final Set<GameFormat.FormatSubType> historicSubTypes = new HashSet<>(Arrays.asList(GameFormat.FormatSubType.BLOCK,
|
||||
GameFormat.FormatSubType.STANDARD,GameFormat.FormatSubType.EXTENDED,GameFormat.FormatSubType.MODERN,
|
||||
GameFormat.FormatSubType.LEGACY, GameFormat.FormatSubType.VINTAGE));
|
||||
|
||||
private Runnable onCloseCallBack;
|
||||
|
||||
public HistoricFormatSelect() {
|
||||
super(Localizer.getInstance().getMessage("lblChooseFormat"));
|
||||
for (GameFormat.FormatType group:GameFormat.FormatType.values()){
|
||||
if (group == GameFormat.FormatType.Historic){
|
||||
if (group == GameFormat.FormatType.HISTORIC){
|
||||
for (GameFormat.FormatSubType subgroup:GameFormat.FormatSubType.values()){
|
||||
if (historicSubTypes.contains(subgroup)){
|
||||
lstFormats.addGroup(group.name() + "-" + subgroup.name());
|
||||
@@ -48,39 +48,39 @@ public class HistoricFormatSelect extends FScreen {
|
||||
}
|
||||
for (GameFormat format: FModel.getFormats().getOrderedList()){
|
||||
switch(format.getFormatType()){
|
||||
case Sanctioned:
|
||||
case SANCTIONED:
|
||||
lstFormats.addItem(format, 0);
|
||||
break;
|
||||
case Casual:
|
||||
case CASUAL:
|
||||
lstFormats.addItem(format, 1);
|
||||
break;
|
||||
case Historic:
|
||||
case HISTORIC:
|
||||
switch (format.getFormatSubType()){
|
||||
case Block:
|
||||
case BLOCK:
|
||||
lstFormats.addItem(format, 2);
|
||||
break;
|
||||
case Standard:
|
||||
case STANDARD:
|
||||
lstFormats.addItem(format, 3);
|
||||
break;
|
||||
case Extended:
|
||||
case EXTENDED:
|
||||
lstFormats.addItem(format, 4);
|
||||
break;
|
||||
case Modern:
|
||||
case MODERN:
|
||||
lstFormats.addItem(format, 5);
|
||||
break;
|
||||
case Legacy:
|
||||
case LEGACY:
|
||||
lstFormats.addItem(format, 6);
|
||||
break;
|
||||
case Vintage:
|
||||
case VINTAGE:
|
||||
lstFormats.addItem(format, 7);
|
||||
break;
|
||||
|
||||
}
|
||||
break;
|
||||
case Digital:
|
||||
case DIGITAL:
|
||||
lstFormats.addItem(format, 8);
|
||||
break;
|
||||
case Custom:
|
||||
case CUSTOM:
|
||||
lstFormats.addItem(format, 9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=P15A
|
||||
Date=2008-04-01
|
||||
Name=15th Anniversary Cards
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=P15A
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PL21
|
||||
Date=2021-01-25
|
||||
Name=2021 Lunar New Year
|
||||
Type=Reprint
|
||||
Type=Promo
|
||||
ScryfallCode=PL21
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[metadata]
|
||||
Code=PAER
|
||||
Date=2017-01-21
|
||||
Date=2017-01-20
|
||||
Name=Aether Revolt Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAER
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PARB
|
||||
Date=2009-04-30
|
||||
Name=Alara Reborn Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PARB
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAKH
|
||||
Date=2017-04-28
|
||||
Name=Amonkhet Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAKH
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[metadata]
|
||||
Code=ATH
|
||||
MciCode=at
|
||||
Date=1998-11
|
||||
Date=1998-11-01
|
||||
Name=Anthologies
|
||||
Type=Other
|
||||
Type=Boxed_Set
|
||||
Border=White
|
||||
Foil=NotSupported
|
||||
ScryfallCode=ATH
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
Code=ATQ
|
||||
Date=1994-03-01
|
||||
Date=1994-03-04
|
||||
Name=Antiquities
|
||||
Code2=AQ
|
||||
MciCode=aq
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAPC
|
||||
Date=2001-06-04
|
||||
Name=Apocalypse Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
CardLang=sa
|
||||
ScryfallCode=PAPC
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
Code=ARN
|
||||
Date=1993-12-21
|
||||
Date=1993-12-17
|
||||
Name=Arabian Nights
|
||||
Code2=AN
|
||||
MciCode=an
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[metadata]
|
||||
Code=E01
|
||||
Date=2017-06-17
|
||||
Date=2017-06-16
|
||||
Name=Archenemy: Nicol Bolas
|
||||
Code2=E01
|
||||
MciCode=E01
|
||||
Type=Other
|
||||
Type=Multiplayer
|
||||
ScryfallCode=E01
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2010-06-18
|
||||
Name=Archenemy
|
||||
Code2=ARC
|
||||
MciCode=arc
|
||||
Type=Other
|
||||
Type=Multiplayer
|
||||
ScryfallCode=ARC
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PARL
|
||||
Date=1996-08-02
|
||||
Name=Arena League 1996
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PARL
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL99
|
||||
Date=1999-01-01
|
||||
Name=Arena League 1999
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL99
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL00
|
||||
Date=2000-01-01
|
||||
Name=Arena League 2000
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL00
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL01
|
||||
Date=2001-01-01
|
||||
Name=Arena League 2001
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL01
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL02
|
||||
Date=2002-01-01
|
||||
Name=Arena League 2002
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL02
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL03
|
||||
Date=2003-01-01
|
||||
Name=Arena League 2003
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL03
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL04
|
||||
Date=2004-01-01
|
||||
Name=Arena League 2004
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL04
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL05
|
||||
Date=2005-01-01
|
||||
Name=Arena League 2005
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL05
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAL06
|
||||
Date=2006-01-01
|
||||
Name=Arena League 2006
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAL06
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
Code=ANA
|
||||
Date=2018-07-12
|
||||
Date=2018-07-14
|
||||
Name=Arena New Player Experience
|
||||
Type=Online
|
||||
ScryfallCode=ANA
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[metadata]
|
||||
Code=ARENA
|
||||
Date=1994-09-26
|
||||
Date=1994-09-01
|
||||
Name=Arena
|
||||
Type=Other
|
||||
Type=Promo
|
||||
ScryfallCode=PHPR
|
||||
|
||||
[cards]
|
||||
1 C Arena
|
||||
2 C Sewers of Estark
|
||||
2 C Sewers of Estark
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PALP
|
||||
Date=1998-09-01
|
||||
Name=Asia Pacific Land Program
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PALP
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PAVR
|
||||
Date=2012-04-28
|
||||
Name=Avacyn Restored Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PAVR
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PSS1
|
||||
Date=2015-10-02
|
||||
Name=BFZ Standard Series
|
||||
Type=Reprint
|
||||
Type=Promo
|
||||
ScryfallCode=PSS1
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=BRB
|
||||
Date=1999-11-12
|
||||
Name=Battle Royale
|
||||
MciCode=br
|
||||
Type=Other
|
||||
Type=Boxed_Set
|
||||
Border=White
|
||||
Foil=NotSupported
|
||||
ScryfallCode=BRB
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[metadata]
|
||||
Code=PBFZ
|
||||
Date=2015-10-03
|
||||
Date=2015-10-02
|
||||
Name=Battle for Zendikar Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PBFZ
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=BBD
|
||||
Date=2018-06-08
|
||||
Name=Battlebond
|
||||
MciCode=bbd
|
||||
Type=Other
|
||||
Type=Draft
|
||||
BoosterCovers=3
|
||||
Booster=10 Common, 3 Uncommon, 1 fromSheet("BBD RareMythic"), 1 BasicLand
|
||||
ScryfallCode=BBD
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=BTD
|
||||
Date=2000-10-01
|
||||
Name=Beatdown
|
||||
MciCode=bd
|
||||
Type=Other
|
||||
Type=Boxed_Set
|
||||
Border=White
|
||||
Foil=NotSupported
|
||||
ScryfallCode=BTD
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PBOK
|
||||
Date=2005-02-04
|
||||
Name=Betrayers of Kamigawa Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PBOK
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PBNG
|
||||
Date=2014-02-01
|
||||
Name=Born of the Gods Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PBNG
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PCEL
|
||||
Date=1996-08-14
|
||||
Name=Celebration Cards
|
||||
Type=Funny
|
||||
Type=Promo
|
||||
ScryfallCode=PCEL
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PCHK
|
||||
Date=2004-10-01
|
||||
Name=Champions of Kamigawa Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PCHK
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PCMP
|
||||
Date=2006-03-18
|
||||
Name=Champs and States
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PCMP
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
Code=CHR
|
||||
Date=1995-07
|
||||
Date=1995-07-01
|
||||
Name=Chronicles
|
||||
Code2=CH
|
||||
MciCode=ch
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
Code=6ED
|
||||
Date=1999-04-28
|
||||
Date=1999-04-21
|
||||
Name=Classic Sixth Edition
|
||||
Code2=6E
|
||||
MciCode=6e
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PCSP
|
||||
Date=2006-07-21
|
||||
Name=Coldsnap Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PCSP
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=CST
|
||||
Date=2006-07-21
|
||||
Name=Coldsnap Theme Decks
|
||||
MciCode=cst
|
||||
Type=Other
|
||||
Type=Boxed_Set
|
||||
ScryfallCode=CST
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2013-11-01
|
||||
Name=Commander 2013
|
||||
Code2=C13
|
||||
MciCode=c13
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C13
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2014-11-07
|
||||
Name=Commander 2014
|
||||
Code2=C14
|
||||
MciCode=c14
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C14
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2015-11-13
|
||||
Name=Commander 2015
|
||||
Code2=C15
|
||||
MciCode=c15
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C15
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2016-11-11
|
||||
Name=Commander 2016
|
||||
Code2=C16
|
||||
MciCode=c16
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C16
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2017-08-25
|
||||
Name=Commander 2017
|
||||
Code2=C17
|
||||
MciCode=c17
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C17
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2018-08-09
|
||||
Name=Commander 2018
|
||||
Code2=C18
|
||||
MciCode=c18
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C18
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2019-08-23
|
||||
Name=Commander 2019
|
||||
Code2=C19
|
||||
MciCode=c19
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C19
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=C20
|
||||
Date=2020-04-17
|
||||
Name=Commander 2020
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C20
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=C21
|
||||
Date=2021-04-23
|
||||
Name=Commander 2021
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=C21
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=CM2
|
||||
Date=2018-06-08
|
||||
Name=Commander Anthology Vol. II
|
||||
MciCode=CM2
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=CM2
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2017-06-09
|
||||
Name=Commander Anthology
|
||||
Code2=CMA
|
||||
MciCode=CMA
|
||||
Type=Other
|
||||
Type=Commander
|
||||
ScryfallCode=CMA
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=CC1
|
||||
Date=2020-12-04
|
||||
Name=Commander Collection Green
|
||||
Type=Reprint
|
||||
Type=Collector_Edition
|
||||
ScryfallCode=CC1
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[metadata]
|
||||
Code=CMR
|
||||
Date=2020-11-11
|
||||
Date=2020-11-20
|
||||
Name=Commander Legends
|
||||
Type=Other
|
||||
Type=Draft
|
||||
Booster=13 Common:fromSheet("CMR cards"), 3 fromSheet("CMR Non-Legendary Uncommons"), 1 fromSheet("CMR Non-Legendary RareMythics"), 1 fromSheet("CMR Foils"), 2 fromSheet("CMR Legendaries")
|
||||
Foil=NotSupported
|
||||
DoublePick=Always
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
Code=TD0
|
||||
Date=2009-08-25
|
||||
Date=2010-11-08
|
||||
Name=Commander Theme Decks
|
||||
MciCode=td0
|
||||
Type=Online
|
||||
|
||||
@@ -5,6 +5,7 @@ Date=2012-11-02
|
||||
Name=Commander's Arsenal
|
||||
ScryfallCode=CM1
|
||||
|
||||
Type=Commander
|
||||
[cards]
|
||||
1 R Chaos Warp
|
||||
2 C Command Tower
|
||||
|
||||
@@ -5,7 +5,7 @@ Name=Commander
|
||||
Code2=COM
|
||||
Alias=CMD
|
||||
MciCode=cmd
|
||||
Type=Other
|
||||
Type=Commander
|
||||
BoosterBox=0
|
||||
ScryfallCode=cmd
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PCON
|
||||
Date=2009-02-06
|
||||
Name=Conflux Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PCON
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PCNS
|
||||
Date=2014-06-06
|
||||
Name=Conspiracy Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PCNS
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2016-08-26
|
||||
Name=Conspiracy: Take the Crown
|
||||
Code2=CN2
|
||||
MciCode=cn2
|
||||
Type=Other
|
||||
Type=Draft
|
||||
BoosterCovers=3
|
||||
Booster=10 Common:!fromSheet("CN2 Not In Normal Slots"), 3 Uncommon:!fromSheet("CN2 Not In Normal Slots"), 1 RareMythic:!fromSheet("CN2 Not In Normal Slots"), 1 fromSheet("CN2 Draft Matters")
|
||||
AdditionalSheetForFoils=fromSheet("CN2 Foil Kaya")
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2014-06-06
|
||||
Name=Conspiracy
|
||||
Code2=CNS
|
||||
MciCode=cns
|
||||
Type=Other
|
||||
Type=Draft
|
||||
BoosterCovers=3
|
||||
Booster=10 Common:!fromSheet("CNS Draft Matters"), 3 Uncommon:!fromSheet("CNS Draft Matters"), 1 RareMythic:!fromSheet("CNS Draft Matters"), 1 fromSheet("CNS Draft Matters")
|
||||
ScryfallCode=CNS
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PM19
|
||||
Date=2018-07-13
|
||||
Name=Core Set 2019 Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PM19
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[metadata]
|
||||
Code=PM20
|
||||
Date=2019-07-02
|
||||
Date=2019-07-12
|
||||
Name=Core Set 2020 Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PM20
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PLGM
|
||||
Date=1995-01-01
|
||||
Name=DCI Legend Membership
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PLGM
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PDKA
|
||||
Date=2012-01-28
|
||||
Name=Dark Ascension Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PDKA
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PDST
|
||||
Date=2004-06-04
|
||||
Name=Darksteel Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PDST
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[metadata]
|
||||
Code=DKM
|
||||
Date=2001-12
|
||||
Date=2001-12-01
|
||||
Name=Deckmasters: Garfield vs. Finkel
|
||||
MciCode=dm
|
||||
Type=Other
|
||||
Type=Boxed_Set
|
||||
Border=White
|
||||
ScryfallCode=DKM
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PDIS
|
||||
Date=2006-05-05
|
||||
Name=Dissension Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PDIS
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PDOM
|
||||
Date=2018-04-27
|
||||
Name=Dominaria Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PDOM
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Code=PDGM
|
||||
Date=2013-04-27
|
||||
Name=Dragon's Maze Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PDGM
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,8 +3,8 @@ Code=DRC94
|
||||
Date=1994-07-15
|
||||
Name=DragonCon 1994
|
||||
Border=Black
|
||||
Type=Other
|
||||
Type=Promo
|
||||
ScryfallCode=PDRC
|
||||
|
||||
[cards]
|
||||
1 S Nalathni Dragon
|
||||
1 S Nalathni Dragon
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[metadata]
|
||||
Code=PDTK
|
||||
Date=2015-03-08
|
||||
Date=2015-03-27
|
||||
Name=Dragons of Tarkir Promos
|
||||
Type=Promos
|
||||
Type=Promo
|
||||
ScryfallCode=PDTK
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DDH
|
||||
Date=2011-09-02
|
||||
Name=Duel Decks: Ajani vs. Nicol Bolas
|
||||
MciCode=ddh
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDH
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DVD
|
||||
Date=2014-12-05
|
||||
Name=Duel Decks Anthology: Divine vs. Demonic
|
||||
MciCode=dvd
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DVD
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=EVG
|
||||
Date=2014-12-05
|
||||
Name=Duel Decks Anthology: Elves vs. Goblins
|
||||
MciCode=evg
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=EVG
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=GVL
|
||||
Date=2014-12-05
|
||||
Name=Duel Decks Anthology: Garruk vs. Liliana
|
||||
MciCode=gvl
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=GVL
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=JVC
|
||||
Date=2014-12-05
|
||||
Name=Duel Decks Anthology: Jace vs. Chandra
|
||||
MciCode=jvc
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=JVC
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2016-02-26
|
||||
Name=Duel Decks: Blessed vs. Cursed
|
||||
Code2=DDQ
|
||||
MciCode=ddq
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDQ
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -4,7 +4,7 @@ Date=2009-04-10
|
||||
Name=Duel Decks: Divine vs. Demonic
|
||||
Code2=DDC
|
||||
MciCode=dvd
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDC
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DDO
|
||||
Date=2015-02-27
|
||||
Name=Duel Decks: Elspeth vs. Kiora
|
||||
MciCode=ddo
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDO
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DDF
|
||||
Date=2010-09-03
|
||||
Name=Duel Decks: Elspeth vs. Tezzeret
|
||||
MciCode=ddf
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDF
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DD1
|
||||
Date=2007-11-16
|
||||
Name=Duel Decks: Elves vs. Goblins
|
||||
MciCode=dd1
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DD1
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DDU
|
||||
Date=2018-04-06
|
||||
Name=Duel Decks: Elves vs. Inventors
|
||||
MciCode=ddu
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDU
|
||||
|
||||
[cards]
|
||||
|
||||
@@ -3,7 +3,7 @@ Code=DDD
|
||||
Date=2009-10-30
|
||||
Name=Duel Decks: Garruk vs. Liliana
|
||||
MciCode=gvl
|
||||
Type=Duel_Decks
|
||||
Type=Duel_Deck
|
||||
ScryfallCode=DDD
|
||||
|
||||
[cards]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user