Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring

This commit is contained in:
leriomaggio
2021-07-06 08:36:40 +01:00
1192 changed files with 9811 additions and 4628 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

@@ -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);
}
}
/**

View File

@@ -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;

View File

@@ -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

View File

@@ -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.

View File

@@ -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() {

View File

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

View File

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

View File

@@ -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.
*

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;