mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ public final class CMatchUI
|
||||
}
|
||||
}
|
||||
|
||||
private SkinImage getPlayerAvatar(final PlayerView p, final int defaultIndex) {
|
||||
public SkinImage getPlayerAvatar(final PlayerView p, final int defaultIndex) {
|
||||
if (avatarImages.containsKey(p.getLobbyPlayerName())) {
|
||||
return ImageCache.getIcon(avatarImages.get(p.getLobbyPlayerName()));
|
||||
}
|
||||
@@ -1029,13 +1029,13 @@ public final class CMatchUI
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> target,
|
||||
public Map<Object, Integer> assignGenericAmount(final CardView effectSource, final Map<Object, Integer> target,
|
||||
final int amount, final boolean atLeastOne, final String amountLabel) {
|
||||
if (amount <= 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
final AtomicReference<Map<GameEntityView, Integer>> result = new AtomicReference<>();
|
||||
final AtomicReference<Map<Object, Integer>> result = new AtomicReference<>();
|
||||
FThreads.invokeInEdtAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -44,11 +44,13 @@ import forge.toolbox.FButton;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FScrollPane;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinImage;
|
||||
import forge.toolbox.FSkin.SkinnedPanel;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import forge.view.FDialog;
|
||||
import forge.view.arcane.CardPanel;
|
||||
import forge.view.arcane.MiscCardPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
/**
|
||||
@@ -116,21 +118,28 @@ public class VAssignCombatDamage {
|
||||
private final MouseAdapter mad = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent evt) {
|
||||
CardView source = ((CardPanel) evt.getSource()).getCard();
|
||||
if (!damage.containsKey(source)) source = null; // to get player instead of fake card
|
||||
SkinnedPanel panel = (SkinnedPanel)evt.getSource();
|
||||
CardView source = null;
|
||||
if (panel instanceof CardPanel) {
|
||||
source = ((CardPanel)panel).getCard();
|
||||
}
|
||||
|
||||
final FSkin.Colors brdrColor = VAssignCombatDamage.this.canAssignTo(source) ? FSkin.Colors.CLR_ACTIVE : FSkin.Colors.CLR_INACTIVE;
|
||||
((CardPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(brdrColor), 2));
|
||||
panel.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(brdrColor), 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent evt) {
|
||||
((CardPanel) evt.getSource()).setBorder((Border)null);
|
||||
((SkinnedPanel) evt.getSource()).setBorder((Border)null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent evt) {
|
||||
CardView source = ((CardPanel) evt.getSource()).getCard(); // will be NULL for player
|
||||
SkinnedPanel panel = (SkinnedPanel)evt.getSource();
|
||||
CardView source = null;
|
||||
if (panel instanceof CardPanel) {
|
||||
source = ((CardPanel)panel).getCard();
|
||||
}
|
||||
|
||||
boolean meta = evt.isControlDown();
|
||||
boolean isLMB = SwingUtilities.isLeftMouseButton(evt);
|
||||
@@ -192,14 +201,7 @@ public class VAssignCombatDamage {
|
||||
final DamageTarget dt = new DamageTarget(null, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build());
|
||||
damage.put(null, dt);
|
||||
defenders.add(dt);
|
||||
CardView fakeCard = null;
|
||||
if (defender instanceof CardView) {
|
||||
fakeCard = (CardView)defender;
|
||||
} else if (defender instanceof PlayerView) {
|
||||
final PlayerView p = (PlayerView)defender;
|
||||
fakeCard = new CardView(-1, null, defender.toString(), p, matchUI.getAvatarImage(p.getLobbyPlayerName()));
|
||||
}
|
||||
addPanelForDefender(pnlDefenders, fakeCard);
|
||||
addPanelForDefender(pnlDefenders, defender);
|
||||
}
|
||||
|
||||
// Add "opponent placeholder" card if trample allowed
|
||||
@@ -257,12 +259,21 @@ public class VAssignCombatDamage {
|
||||
* @param pnlDefenders
|
||||
* @param defender
|
||||
*/
|
||||
private void addPanelForDefender(final JPanel pnlDefenders, final CardView defender) {
|
||||
final CardPanel cp = new CardPanel(matchUI, defender);
|
||||
cp.setCardBounds(0, 0, 105, 150);
|
||||
cp.setOpaque(true);
|
||||
pnlDefenders.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
|
||||
cp.addMouseListener(mad);
|
||||
private void addPanelForDefender(final JPanel pnlDefenders, final GameEntityView defender) {
|
||||
if (defender instanceof CardView) {
|
||||
final CardPanel cp = new CardPanel(matchUI, (CardView)defender);
|
||||
cp.setCardBounds(0, 0, 105, 150);
|
||||
cp.setOpaque(true);
|
||||
pnlDefenders.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
|
||||
cp.addMouseListener(mad);
|
||||
} else if (defender instanceof PlayerView) {
|
||||
final PlayerView p = (PlayerView)defender;
|
||||
SkinImage playerAvatar = matchUI.getPlayerAvatar(p, 0);
|
||||
final MiscCardPanel mp = new MiscCardPanel(matchUI, p.getName(), playerAvatar);
|
||||
mp.setCardBounds(0, 0, 105, 150);
|
||||
pnlDefenders.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
|
||||
mp.addMouseListener(mad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,27 +34,26 @@ import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
import forge.game.GameEntityView;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.player.PlayerView;
|
||||
import forge.gui.SOverlayUtils;
|
||||
import forge.localinstance.skin.FSkinProp;
|
||||
import forge.toolbox.FButton;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FScrollPane;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinImage;
|
||||
import forge.toolbox.FSkin.SkinnedPanel;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import forge.view.FDialog;
|
||||
import forge.view.arcane.CardPanel;
|
||||
import forge.view.arcane.MiscCardPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
/**
|
||||
* Assembles Swing components of assign damage dialog.
|
||||
*
|
||||
* This needs a JDialog to maintain a modal state.
|
||||
* Without the modal state, the PhaseHandler automatically
|
||||
* moves forward to phase Main2 without assigning damage.
|
||||
* Assembles Swing components of assign generic amount dialog.
|
||||
*
|
||||
* <br><br><i>(V at beginning of class name denotes a view class.)</i>
|
||||
*/
|
||||
@@ -72,17 +71,18 @@ public class VAssignGenericAmount {
|
||||
|
||||
private final String lblAmount;
|
||||
private final JLabel lblTotalAmount;
|
||||
private final boolean atLeastOne;
|
||||
// Label Buttons
|
||||
private final FButton btnOK = new FButton(localizer.getMessage("lblOk"));
|
||||
private final FButton btnReset = new FButton(localizer.getMessage("lblReset"));
|
||||
|
||||
private static class AssignTarget {
|
||||
public final GameEntityView entity;
|
||||
public final Object entity;
|
||||
public final JLabel label;
|
||||
public final int max;
|
||||
public int amount;
|
||||
|
||||
public AssignTarget(final GameEntityView e, final JLabel lbl, int max0) {
|
||||
public AssignTarget(final Object e, final JLabel lbl, int max0) {
|
||||
entity = e;
|
||||
label = lbl;
|
||||
max = max0;
|
||||
@@ -91,38 +91,40 @@ public class VAssignGenericAmount {
|
||||
}
|
||||
|
||||
private final List<AssignTarget> targetsList = new ArrayList<>();
|
||||
private final Map<GameEntityView, AssignTarget> targetsMap = new HashMap<>();
|
||||
private final Map<SkinnedPanel, AssignTarget> targetsMap = new HashMap<>();
|
||||
|
||||
// Mouse actions
|
||||
private final MouseAdapter mad = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent evt) {
|
||||
((CardPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_ACTIVE), 2));
|
||||
((SkinnedPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_ACTIVE), 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent evt) {
|
||||
((CardPanel) evt.getSource()).setBorder((Border)null);
|
||||
((SkinnedPanel) evt.getSource()).setBorder((Border)null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent evt) {
|
||||
CardView source = ((CardPanel) evt.getSource()).getCard(); // will be NULL for player
|
||||
SkinnedPanel panel = (SkinnedPanel)evt.getSource();
|
||||
AssignTarget at = targetsMap.get(panel);
|
||||
|
||||
boolean meta = evt.isControlDown();
|
||||
boolean isLMB = SwingUtilities.isLeftMouseButton(evt);
|
||||
boolean isRMB = SwingUtilities.isRightMouseButton(evt);
|
||||
|
||||
if ( isLMB || isRMB)
|
||||
assignAmountTo(source, meta, isLMB);
|
||||
assignAmountTo(at, meta, isLMB);
|
||||
}
|
||||
};
|
||||
|
||||
public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
|
||||
public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map<Object, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
|
||||
this.matchUI = matchUI;
|
||||
dlg.setTitle(localizer.getMessage("lbLAssignAmountForEffect", amountLabel, effectSource.toString()));
|
||||
|
||||
totalAmountToAssign = amount;
|
||||
this.atLeastOne = atLeastOne;
|
||||
|
||||
lblAmount = amountLabel;
|
||||
lblTotalAmount = new FLabel.Builder().text(localizer.getMessage("lblTotalAmountText", lblAmount)).build();
|
||||
@@ -153,7 +155,7 @@ public class VAssignGenericAmount {
|
||||
final FScrollPane scrTargets = new FScrollPane(pnlTargets, false);
|
||||
|
||||
// Top row of cards...
|
||||
for (final Map.Entry<GameEntityView, Integer> e : targets.entrySet()) {
|
||||
for (final Map.Entry<Object, Integer> e : targets.entrySet()) {
|
||||
int maxAmount = e.getValue() != null ? e.getValue() : amount;
|
||||
final AssignTarget at = new AssignTarget(e.getKey(), new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build(), maxAmount);
|
||||
addPanelForTarget(pnlTargets, at);
|
||||
@@ -161,13 +163,17 @@ public class VAssignGenericAmount {
|
||||
|
||||
// ... bottom row of labels.
|
||||
for (final AssignTarget l : targetsList) {
|
||||
pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px");
|
||||
if (l.entity instanceof Byte) {
|
||||
pnlTargets.add(l.label, "w 100px!, h 30px!, gap 5px 5px 0 5px");
|
||||
} else {
|
||||
pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px");
|
||||
}
|
||||
}
|
||||
|
||||
btnOK.addActionListener(new ActionListener() {
|
||||
@Override public void actionPerformed(ActionEvent arg0) { finish(); } });
|
||||
btnReset.addActionListener(new ActionListener() {
|
||||
@Override public void actionPerformed(ActionEvent arg0) { resetAssignedAmount(); initialAssignAmount(atLeastOne); } });
|
||||
@Override public void actionPerformed(ActionEvent arg0) { resetAssignedAmount(); initialAssignAmount(); } });
|
||||
|
||||
// Final UI layout
|
||||
pnlMain.setLayout(new MigLayout("insets 0, gap 0, wrap 2, ax center"));
|
||||
@@ -185,7 +191,7 @@ public class VAssignGenericAmount {
|
||||
|
||||
pnlMain.getRootPane().setDefaultButton(btnOK);
|
||||
|
||||
initialAssignAmount(atLeastOne);
|
||||
initialAssignAmount();
|
||||
SOverlayUtils.showOverlay();
|
||||
|
||||
dlg.setUndecorated(true);
|
||||
@@ -197,26 +203,47 @@ public class VAssignGenericAmount {
|
||||
}
|
||||
|
||||
private void addPanelForTarget(final JPanel pnlTargets, final AssignTarget at) {
|
||||
CardView cv = null;
|
||||
if (at.entity instanceof CardView) {
|
||||
cv = (CardView)at.entity;
|
||||
final CardPanel cp = new CardPanel(matchUI, (CardView)at.entity);
|
||||
cp.setCardBounds(0, 0, 105, 150);
|
||||
cp.setOpaque(true);
|
||||
pnlTargets.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
|
||||
cp.addMouseListener(mad);
|
||||
targetsMap.put(cp, at);
|
||||
} else if (at.entity instanceof PlayerView) {
|
||||
final PlayerView p = (PlayerView)at.entity;
|
||||
cv = new CardView(-1, null, at.entity.toString(), p, matchUI.getAvatarImage(p.getLobbyPlayerName()));
|
||||
} else {
|
||||
return;
|
||||
SkinImage playerAvatar = matchUI.getPlayerAvatar(p, 0);
|
||||
final MiscCardPanel mp = new MiscCardPanel(matchUI, p.getName(), playerAvatar);
|
||||
mp.setCardBounds(0, 0, 105, 150);
|
||||
pnlTargets.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
|
||||
mp.addMouseListener(mad);
|
||||
targetsMap.put(mp, at);
|
||||
} else if (at.entity instanceof Byte) {
|
||||
SkinImage manaSymbol;
|
||||
Byte color = (Byte) at.entity;
|
||||
if (color == MagicColor.WHITE) {
|
||||
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_W);
|
||||
} else if (color == MagicColor.BLUE) {
|
||||
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_U);
|
||||
} else if (color == MagicColor.BLACK) {
|
||||
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_B);
|
||||
} else if (color == MagicColor.RED) {
|
||||
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_R);
|
||||
} else if (color == MagicColor.GREEN) {
|
||||
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_G);
|
||||
} else { // Should never come here, but add this to avoid compile error
|
||||
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_COLORLESS);
|
||||
}
|
||||
final MiscCardPanel mp = new MiscCardPanel(matchUI, "", manaSymbol);
|
||||
mp.setCardBounds(0, 0, 70, 70);
|
||||
pnlTargets.add(mp, "w 100px!, h 150px!, gap 5px 5px 3px 3px, ax center");
|
||||
mp.addMouseListener(mad);
|
||||
targetsMap.put(mp, at);
|
||||
}
|
||||
final CardPanel cp = new CardPanel(matchUI, cv);
|
||||
cp.setCardBounds(0, 0, 105, 150);
|
||||
cp.setOpaque(true);
|
||||
pnlTargets.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
|
||||
cp.addMouseListener(mad);
|
||||
targetsMap.put(cv, at);
|
||||
targetsList.add(at);
|
||||
}
|
||||
|
||||
private void assignAmountTo(CardView source, final boolean meta, final boolean isAdding) {
|
||||
AssignTarget at = targetsMap.get(source);
|
||||
private void assignAmountTo(AssignTarget at, final boolean meta, final boolean isAdding) {
|
||||
int assigned = at.amount;
|
||||
int leftToAssign = Math.max(0, at.max - assigned);
|
||||
int amountToAdd = isAdding ? 1 : -1;
|
||||
@@ -234,6 +261,9 @@ public class VAssignGenericAmount {
|
||||
if (amountToAdd > remainingAmount) {
|
||||
amountToAdd = remainingAmount;
|
||||
}
|
||||
if (atLeastOne && assigned + amountToAdd < 1) {
|
||||
amountToAdd = 1 - assigned;
|
||||
}
|
||||
|
||||
if (0 == amountToAdd || amountToAdd + assigned < 0) {
|
||||
return;
|
||||
@@ -243,7 +273,7 @@ public class VAssignGenericAmount {
|
||||
updateLabels();
|
||||
}
|
||||
|
||||
private void initialAssignAmount(boolean atLeastOne) {
|
||||
private void initialAssignAmount() {
|
||||
if (!atLeastOne) {
|
||||
updateLabels();
|
||||
return;
|
||||
@@ -305,8 +335,8 @@ public class VAssignGenericAmount {
|
||||
SOverlayUtils.hideOverlay();
|
||||
}
|
||||
|
||||
public Map<GameEntityView, Integer> getAssignedMap() {
|
||||
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size());
|
||||
public Map<Object, Integer> getAssignedMap() {
|
||||
Map<Object, Integer> result = new HashMap<>(targetsList.size());
|
||||
for (AssignTarget at : targetsList)
|
||||
result.put(at.entity, at.amount);
|
||||
return result;
|
||||
|
||||
@@ -52,6 +52,7 @@ public final class CDev implements ICDoc {
|
||||
view.getLblRemoveFromGame().addMouseListener(madRemoveFromGame);
|
||||
view.getLblRiggedRoll().addMouseListener(madRiggedRoll);
|
||||
view.getLblWalkTo().addMouseListener(madWalkToPlane);
|
||||
view.getLblAskAI().addMouseListener(madAskAI);
|
||||
}
|
||||
public IGameController getController() {
|
||||
return matchUI.getGameController();
|
||||
@@ -309,6 +310,16 @@ public final class CDev implements ICDoc {
|
||||
getController().cheat().planeswalkTo();
|
||||
}
|
||||
|
||||
private final MouseListener madAskAI = new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent e) {
|
||||
askAI();
|
||||
}
|
||||
};
|
||||
public void askAI() {
|
||||
getController().cheat().askAI();
|
||||
}
|
||||
|
||||
//========== End mouse listener inits
|
||||
|
||||
@Override
|
||||
|
||||
@@ -81,6 +81,9 @@ public class VDev implements IVDoc<CDev>, IDevListener {
|
||||
private final DevLabel lblRiggedRoll = new DevLabel(Localizer.getInstance().getMessage("lblRiggedRoll"));
|
||||
private final DevLabel lblWalkTo = new DevLabel(Localizer.getInstance().getMessage("lblWalkTo"));
|
||||
|
||||
private final DevLabel lblAskAI = new DevLabel(Localizer.getInstance().getMessage("lblAskAI"));
|
||||
|
||||
|
||||
private final CDev controller;
|
||||
|
||||
//========= Constructor
|
||||
@@ -116,6 +119,7 @@ public class VDev implements IVDoc<CDev>, IDevListener {
|
||||
viewport.add(this.lblUntapPermanent, halfConstraints);
|
||||
viewport.add(this.lblRiggedRoll, halfConstraintsLeft);
|
||||
viewport.add(this.lblWalkTo, halfConstraints);
|
||||
viewport.add(this.lblAskAI, halfConstraintsLeft);
|
||||
}
|
||||
|
||||
//========= Overridden methods
|
||||
@@ -294,6 +298,10 @@ public class VDev implements IVDoc<CDev>, IDevListener {
|
||||
return this.lblWalkTo;
|
||||
}
|
||||
|
||||
public DevLabel getLblAskAI() {
|
||||
return this.lblAskAI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Labels that act as buttons which control dev mode functions. Labels are
|
||||
* used to support multiline text.
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -46,7 +46,7 @@ import forge.localinstance.properties.ForgeConstants;
|
||||
/**
|
||||
* SoundSystem - a simple sound playback system for Forge.
|
||||
* Do not use directly. Instead, use the {@link forge.sound.SoundEffectType} enumeration.
|
||||
*
|
||||
*
|
||||
* @author Agetian
|
||||
*/
|
||||
public class AudioClip implements IAudioClip {
|
||||
@@ -104,9 +104,9 @@ public class AudioClip implements IAudioClip {
|
||||
|
||||
private ClipWrapper getIdleClip() {
|
||||
return clips.stream()
|
||||
.filter(clip -> !clip.isRunning())
|
||||
.findFirst()
|
||||
.orElseGet(this::addClip);
|
||||
.filter(clip -> !clip.isRunning())
|
||||
.findFirst()
|
||||
.orElseGet(this::addClip);
|
||||
}
|
||||
|
||||
private ClipWrapper addClip() {
|
||||
|
||||
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();
|
||||
|
||||
@@ -510,6 +510,13 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
}
|
||||
|
||||
if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("In Room:");
|
||||
markers.add(card.getCurrentRoom());
|
||||
drawMarkersTabs(g, markers);
|
||||
}
|
||||
|
||||
final int combatXSymbols = (cardXOffset + (cardWidth / 4)) - 16;
|
||||
final int stateXSymbols = (cardXOffset + (cardWidth / 2)) - 16;
|
||||
final int ySymbols = (cardYOffset + cardHeight) - (cardHeight / 8) - 16;
|
||||
@@ -840,6 +847,57 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
}
|
||||
|
||||
private void drawMarkersTabs(final Graphics g, List<String> markers) {
|
||||
|
||||
final Dimension imgSize = calculateImageSize();
|
||||
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
|
||||
|
||||
final int spaceFromTopOfCard = titleY + 60;
|
||||
final int markerBoxHeight = 24;
|
||||
final int markerBoxBaseWidth = 14;
|
||||
final int markerBoxSpacing = 2;
|
||||
|
||||
int currentMarker = 0;
|
||||
|
||||
FontMetrics smallFontMetrics = g.getFontMetrics(smallCounterFont);
|
||||
|
||||
for (String marker : markers) {
|
||||
|
||||
final int markerBoxRealWidth = markerBoxBaseWidth + smallFontMetrics.stringWidth(marker);
|
||||
final int markerYOffset;
|
||||
|
||||
if (ForgeConstants.CounterDisplayLocation.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_LOCATION)) == ForgeConstants.CounterDisplayLocation.TOP) {
|
||||
markerYOffset = cardYOffset + spaceFromTopOfCard - markerBoxHeight + currentMarker++ * (markerBoxHeight + markerBoxSpacing);
|
||||
} else {
|
||||
markerYOffset = cardYOffset + cardHeight - spaceFromTopOfCard / 2 - markerBoxHeight + currentMarker++ * (markerBoxHeight + markerBoxSpacing);
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
g.setColor(new Color(0, 0, 0, 255));
|
||||
} else {
|
||||
g.setColor(new Color(0, 0, 0, 200));
|
||||
}
|
||||
|
||||
RoundRectangle2D markerArea = new RoundRectangle2D.Float(cardXOffset, markerYOffset, markerBoxRealWidth, markerBoxHeight, 9, 9);
|
||||
((Graphics2D) g).fill(markerArea);
|
||||
|
||||
g.fillRect(cardXOffset, markerYOffset, 9, markerBoxHeight);
|
||||
|
||||
if (isSelected) {
|
||||
g.setColor(new Color(200, 200, 200));
|
||||
} else {
|
||||
g.setColor(new Color(200, 200, 200, 180));
|
||||
}
|
||||
|
||||
Rectangle nameBounds = markerArea.getBounds();
|
||||
nameBounds.x += 8;
|
||||
nameBounds.y -= 1;
|
||||
nameBounds.width = 43;
|
||||
drawVerticallyCenteredString(g, marker, nameBounds, smallCounterFont, smallFontMetrics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a String justified to the left of the rectangle, centered vertically.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package forge.view.arcane;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import forge.localinstance.properties.ForgePreferences.FPref;
|
||||
import forge.model.FModel;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FSkin.SkinImage;
|
||||
import forge.toolbox.FSkin.SkinnedPanel;
|
||||
import forge.view.arcane.util.OutlinedLabel;
|
||||
|
||||
|
||||
public class MiscCardPanel extends SkinnedPanel {
|
||||
private static final float ROT_CENTER_TO_TOP_CORNER = 1.0295630140987000315797369464196f;
|
||||
private static final float ROT_CENTER_TO_BOTTOM_CORNER = 0.7071067811865475244008443621048f;
|
||||
|
||||
private final CMatchUI matchUI;
|
||||
private final String label;
|
||||
private final FLabel image;
|
||||
|
||||
private OutlinedLabel titleText;
|
||||
private int cardXOffset, cardYOffset, cardWidth, cardHeight;
|
||||
|
||||
public MiscCardPanel(final CMatchUI matchUI, final String label, final SkinImage image) {
|
||||
this.matchUI = matchUI;
|
||||
this.label = label;
|
||||
this.image = new FLabel.Builder().icon(image).build();
|
||||
|
||||
setBackground(Color.black);
|
||||
setOpaque(true);
|
||||
|
||||
add(this.image);
|
||||
createCardNameOverlay();
|
||||
}
|
||||
|
||||
public CMatchUI getMatchUI() {
|
||||
return matchUI;
|
||||
}
|
||||
|
||||
private void createCardNameOverlay() {
|
||||
titleText = new OutlinedLabel();
|
||||
titleText.setFont(getFont().deriveFont(Font.BOLD, 13f));
|
||||
titleText.setForeground(Color.white);
|
||||
titleText.setGlow(Color.black);
|
||||
titleText.setWrap(true);
|
||||
titleText.setText(label);
|
||||
add(titleText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void paint(final Graphics g) {
|
||||
if (!isValid()) {
|
||||
super.validate();
|
||||
}
|
||||
super.paint(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void doLayout() {
|
||||
final Point imgPos = new Point(cardXOffset, cardYOffset);
|
||||
final Dimension imgSize = new Dimension(cardWidth, cardHeight);
|
||||
|
||||
image.setLocation(imgPos);
|
||||
image.setSize(imgSize);
|
||||
|
||||
displayCardNameOverlay(showCardNameOverlay(), imgSize, imgPos);
|
||||
}
|
||||
|
||||
private void displayCardNameOverlay(final boolean isVisible, final Dimension imgSize, final Point imgPos) {
|
||||
if (isVisible) {
|
||||
final int titleX = Math.round(imgSize.width * (24f / 480));
|
||||
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
|
||||
final int titleH = Math.round(imgSize.height * (360f / 640));
|
||||
titleText.setBounds(imgPos.x + titleX, imgPos.y + titleY + 2, imgSize.width - 2 * titleX, titleH - titleY);
|
||||
}
|
||||
titleText.setVisible(isVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public final void setCardBounds(final int x, final int y, int width, int height) {
|
||||
cardWidth = width;
|
||||
cardHeight = height;
|
||||
final int rotCenterX = Math.round(width / 2f);
|
||||
final int rotCenterY = height - rotCenterX;
|
||||
final int rotCenterToTopCorner = Math.round(width * ROT_CENTER_TO_TOP_CORNER);
|
||||
final int rotCenterToBottomCorner = Math.round(width * ROT_CENTER_TO_BOTTOM_CORNER);
|
||||
final int xOffset = rotCenterX - rotCenterToBottomCorner;
|
||||
final int yOffset = rotCenterY - rotCenterToTopCorner;
|
||||
cardXOffset = -xOffset;
|
||||
cardYOffset = -yOffset;
|
||||
width = -xOffset + rotCenterX + rotCenterToTopCorner;
|
||||
height = -yOffset + rotCenterY + rotCenterToBottomCorner;
|
||||
setBounds(x + xOffset, y + yOffset, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void repaint() {
|
||||
final Rectangle b = getBounds();
|
||||
final JRootPane rootPane = SwingUtilities.getRootPane(this);
|
||||
if (rootPane == null) {
|
||||
return;
|
||||
}
|
||||
final Point p = SwingUtilities.convertPoint(getParent(), b.x, b.y, rootPane);
|
||||
rootPane.repaint(p.x, p.y, b.width, b.height);
|
||||
}
|
||||
|
||||
private static boolean isPreferenceEnabled(final FPref preferenceName) {
|
||||
return FModel.getPreferences().getPrefBoolean(preferenceName);
|
||||
}
|
||||
|
||||
private boolean isShowingOverlays() {
|
||||
return isPreferenceEnabled(FPref.UI_SHOW_CARD_OVERLAYS);
|
||||
}
|
||||
|
||||
private boolean showCardNameOverlay() {
|
||||
return isShowingOverlays() && isPreferenceEnabled(FPref.UI_OVERLAY_CARD_NAME);
|
||||
}
|
||||
|
||||
public void repaintOverlays() {
|
||||
repaint();
|
||||
doLayout();
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(1);
|
||||
Card sorin = addCard("Sorin, Solemn Visitor", p);
|
||||
sorin.addCounter(CounterEnumType.LOYALTY, 5, p, false, null);
|
||||
sorin.addCounter(CounterEnumType.LOYALTY, 5, p, null, false, null);
|
||||
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
game.getAction().checkStateEffects(true);
|
||||
@@ -262,7 +262,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
String bearCardName = "Runeclaw Bear";
|
||||
addCard(bearCardName, p);
|
||||
Card gideon = addCard("Gideon, Ally of Zendikar", p);
|
||||
gideon.addCounter(CounterEnumType.LOYALTY, 4, p, false, null);
|
||||
gideon.addCounter(CounterEnumType.LOYALTY, 4, p, null, false, null);
|
||||
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
game.getAction().checkStateEffects(true);
|
||||
@@ -385,7 +385,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
Game game = initAndCreateGame();
|
||||
Player p = game.getPlayers().get(1);
|
||||
Card sarkhan = addCard(sarkhanCardName, p);
|
||||
sarkhan.addCounter(CounterEnumType.LOYALTY, 4, p, false, null);
|
||||
sarkhan.addCounter(CounterEnumType.LOYALTY, 4, p, null, false, null);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
game.getAction().checkStateEffects(true);
|
||||
|
||||
@@ -419,7 +419,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
addCard(ornithoperCardName, p);
|
||||
addCard(bearCardName, p);
|
||||
Card ajani = addCard(ajaniCardName, p);
|
||||
ajani.addCounter(CounterEnumType.LOYALTY, 4, p, false, null);
|
||||
ajani.addCounter(CounterEnumType.LOYALTY, 4, p, null, false, null);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
game.getAction().checkStateEffects(true);
|
||||
|
||||
@@ -450,7 +450,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
SpellAbility boltSA = boltCard.getFirstSpellAbility();
|
||||
|
||||
Card ajani = addCard(ajaniCardName, p);
|
||||
ajani.addCounter(CounterEnumType.LOYALTY, 8, p, false, null);
|
||||
ajani.addCounter(CounterEnumType.LOYALTY, 8, p, null, false, null);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
game.getAction().checkStateEffects(true);
|
||||
|
||||
@@ -499,7 +499,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
addCard("Swamp", p);
|
||||
addCard("Swamp", p);
|
||||
Card depths = addCard("Dark Depths", p);
|
||||
depths.addCounter(CounterEnumType.ICE, 10, p, false, null);
|
||||
depths.addCounter(CounterEnumType.ICE, 10, p, null, false, null);
|
||||
Card thespian = addCard("Thespian's Stage", p);
|
||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||
game.getAction().checkStateEffects(true);
|
||||
@@ -2176,7 +2176,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
Player p = game.getPlayers().get(0);
|
||||
|
||||
Card polukranos = addCard(polukranosCardName, p);
|
||||
polukranos.addCounter(CounterEnumType.P1P1, 6, p, false, null);
|
||||
polukranos.addCounter(CounterEnumType.P1P1, 6, p, null, false, null);
|
||||
addCard(hydraCardName, p);
|
||||
addCard(leylineCardName, p);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
@@ -2220,7 +2220,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
||||
}
|
||||
|
||||
Card nishoba = addCard(nishobaName, p1);
|
||||
nishoba.addCounter(CounterEnumType.P1P1, 7, p1, false, null);
|
||||
nishoba.addCounter(CounterEnumType.P1P1, 7, p1, null, false, null);
|
||||
addCard(capridorName, p1);
|
||||
Card pridemate = addCard(pridemateName, p1);
|
||||
Card indestructibility = addCard(indestructibilityName, p1);
|
||||
|
||||
@@ -80,7 +80,7 @@ import forge.util.collect.FCollectionView;
|
||||
/**
|
||||
* Default harmless implementation for tests.
|
||||
* Test-specific behaviour can easily be added by mocking (parts of) this class.
|
||||
*
|
||||
*
|
||||
* Note that the current PlayerController implementations seem to be responsible for handling some game logic,
|
||||
* and even aside from that, they are theoretically capable of making illegal choices (which are then not blocked by the real game logic).
|
||||
* Test cases that need to override the default behaviour of this class should make sure to do so in a way that does not invalidate their correctness.
|
||||
@@ -151,6 +151,10 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
throw new IllegalStateException("Erring on the side of caution here...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
|
||||
throw new IllegalStateException("Erring on the side of caution here...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||
@@ -422,7 +426,7 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
@Override
|
||||
public List<SpellAbility> chooseSpellAbilityToPlay() {
|
||||
//TODO: This method has to return the spellability chosen by player
|
||||
// It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface
|
||||
// It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface
|
||||
if (playerActions != null) {
|
||||
CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable(player, getGame(), CastSpellFromHandAction.class);
|
||||
if (castSpellFromHand != null) {
|
||||
@@ -476,7 +480,7 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
|
||||
return Iterables.getFirst(colors, MagicColor.WHITE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
|
||||
return Iterables.getFirst(colors, (byte)0);
|
||||
@@ -551,7 +555,7 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
ComputerUtil.playStack(sa, player, getGame());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
@@ -583,7 +587,7 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
} else {
|
||||
ComputerUtil.playStack(tgtSA, player, getGame());
|
||||
}
|
||||
} else
|
||||
} else
|
||||
return false; // didn't play spell
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user