mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Refactor logic for advanced search to make it re-usable for desktop
This commit is contained in:
@@ -273,4 +273,9 @@ public class GuiDesktop implements IGuiBase {
|
|||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runBackgroundTask(String message, Runnable task) {
|
||||||
|
//TODO: Show loading overlay
|
||||||
|
FThreads.invokeInBackgroundThread(task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ import forge.interfaces.IGuiGame;
|
|||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import forge.match.HostedMatch;
|
import forge.match.HostedMatch;
|
||||||
import forge.properties.ForgeConstants;
|
import forge.properties.ForgeConstants;
|
||||||
|
import forge.screens.LoadingOverlay;
|
||||||
import forge.screens.match.MatchController;
|
import forge.screens.match.MatchController;
|
||||||
import forge.screens.quest.QuestMenu;
|
import forge.screens.quest.QuestMenu;
|
||||||
import forge.screens.settings.GuiDownloader;
|
import forge.screens.settings.GuiDownloader;
|
||||||
@@ -279,4 +280,9 @@ public class GuiMobile implements IGuiBase {
|
|||||||
public HostedMatch hostMatch() {
|
public HostedMatch hostMatch() {
|
||||||
return MatchController.hostMatch();
|
return MatchController.hostMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runBackgroundTask(String message, Runnable task) {
|
||||||
|
LoadingOverlay.runBackgroundTask(message, task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
package forge.itemmanager.filters;
|
package forge.itemmanager.filters;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
|
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.FThreads;
|
|
||||||
import forge.Forge;
|
import forge.Forge;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.interfaces.IButton;
|
||||||
import forge.item.InventoryItem;
|
import forge.item.InventoryItem;
|
||||||
import forge.itemmanager.AdvancedSearch;
|
import forge.itemmanager.AdvancedSearch;
|
||||||
import forge.itemmanager.ItemManager;
|
import forge.itemmanager.ItemManager;
|
||||||
import forge.itemmanager.AdvancedSearch.FilterOption;
|
|
||||||
import forge.menu.FTooltip;
|
import forge.menu.FTooltip;
|
||||||
import forge.screens.FScreen;
|
import forge.screens.FScreen;
|
||||||
import forge.screens.LoadingOverlay;
|
|
||||||
import forge.toolbox.FContainer;
|
import forge.toolbox.FContainer;
|
||||||
import forge.toolbox.FDisplayObject;
|
import forge.toolbox.FDisplayObject;
|
||||||
import forge.toolbox.FEvent;
|
import forge.toolbox.FEvent;
|
||||||
@@ -29,13 +23,14 @@ import forge.util.Callback;
|
|||||||
|
|
||||||
|
|
||||||
public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T> {
|
public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T> {
|
||||||
private final List<Object> expression = new ArrayList<Object>();
|
private final AdvancedSearch.Model<T> model;
|
||||||
|
|
||||||
private FiltersLabel label;
|
private FiltersLabel label;
|
||||||
private EditScreen editScreen;
|
private EditScreen editScreen;
|
||||||
|
|
||||||
public AdvancedSearchFilter(ItemManager<? super T> itemManager0) {
|
public AdvancedSearchFilter(ItemManager<? super T> itemManager0) {
|
||||||
super(itemManager0);
|
super(itemManager0);
|
||||||
|
model = new AdvancedSearch.Model<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -46,76 +41,14 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final Predicate<T> buildPredicate() {
|
protected final Predicate<T> buildPredicate() {
|
||||||
if (expression.isEmpty()) {
|
if (model.isEmpty()) {
|
||||||
return Predicates.alwaysTrue();
|
return Predicates.alwaysTrue();
|
||||||
}
|
}
|
||||||
return getPredicate();
|
return model.getPredicate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Predicate<T> getPredicate() {
|
public Predicate<? super T> getPredicate() {
|
||||||
return getPredicatePiece(new ExpressionIterator());
|
return model.getPredicate();
|
||||||
}
|
|
||||||
|
|
||||||
private class ExpressionIterator {
|
|
||||||
private int index;
|
|
||||||
private boolean hasNext() {
|
|
||||||
return index < expression.size();
|
|
||||||
}
|
|
||||||
private ExpressionIterator next() {
|
|
||||||
index++;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
private Object get() {
|
|
||||||
return expression.get(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Predicate<T> getPredicatePiece(ExpressionIterator iterator) {
|
|
||||||
Predicate<T> pred = null;
|
|
||||||
Predicate<T> predPiece = null;
|
|
||||||
Operator operator = null;
|
|
||||||
boolean applyNot = false;
|
|
||||||
|
|
||||||
for (; iterator.hasNext(); iterator.next()) {
|
|
||||||
Object piece = iterator.get();
|
|
||||||
if (piece.equals(Operator.OPEN_PAREN)) {
|
|
||||||
predPiece = getPredicatePiece(iterator.next());
|
|
||||||
}
|
|
||||||
else if (piece.equals(Operator.CLOSE_PAREN)) {
|
|
||||||
return pred;
|
|
||||||
}
|
|
||||||
else if (piece.equals(Operator.AND)) {
|
|
||||||
operator = Operator.AND;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (piece.equals(Operator.OR)) {
|
|
||||||
operator = Operator.OR;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (piece.equals(Operator.NOT)) {
|
|
||||||
applyNot = !applyNot;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
predPiece = ((AdvancedSearch.Filter<T>) piece).getPredicate();
|
|
||||||
}
|
|
||||||
if (applyNot) {
|
|
||||||
predPiece = Predicates.not(predPiece);
|
|
||||||
applyNot = false;
|
|
||||||
}
|
|
||||||
if (pred == null) {
|
|
||||||
pred = predPiece;
|
|
||||||
}
|
|
||||||
else if (operator == Operator.AND) {
|
|
||||||
pred = Predicates.and(pred, predPiece);
|
|
||||||
}
|
|
||||||
else if (operator == Operator.OR) {
|
|
||||||
pred = Predicates.or(pred, predPiece);
|
|
||||||
}
|
|
||||||
operator = null;
|
|
||||||
}
|
|
||||||
return pred;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void edit() {
|
public void edit() {
|
||||||
@@ -125,80 +58,21 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
Forge.openScreen(editScreen);
|
Forge.openScreen(editScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void updateLabel() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Filter: ");
|
|
||||||
if (expression.isEmpty()) {
|
|
||||||
builder.append("(none)");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int prevFilterEndIdx = -1;
|
|
||||||
AdvancedSearch.Filter<T> filter, prevFilter = null;
|
|
||||||
for (Object piece : expression) {
|
|
||||||
if (piece instanceof AdvancedSearch.Filter) {
|
|
||||||
filter = (AdvancedSearch.Filter<T>)piece;
|
|
||||||
if (filter.canMergeCaptionWith(prevFilter)) {
|
|
||||||
//convert boolean operators between filters to lowercase
|
|
||||||
builder.replace(prevFilterEndIdx, builder.length(), builder.substring(prevFilterEndIdx).toLowerCase());
|
|
||||||
//append only values for filter
|
|
||||||
builder.append(filter.extractValuesFromCaption());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
builder.append(filter);
|
|
||||||
}
|
|
||||||
prevFilter = filter;
|
|
||||||
prevFilterEndIdx = builder.length();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (piece.equals(Operator.OPEN_PAREN) || piece.equals(Operator.CLOSE_PAREN)) {
|
|
||||||
prevFilter = null; //prevent merging filters with parentheses in between
|
|
||||||
}
|
|
||||||
builder.append(piece);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label.setText(builder.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTooltip() {
|
|
||||||
if (expression.isEmpty()) { return ""; }
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Filter:\n");
|
|
||||||
|
|
||||||
String indent = "";
|
|
||||||
|
|
||||||
for (Object piece : expression) {
|
|
||||||
if (piece.equals(Operator.CLOSE_PAREN) && !indent.isEmpty()) {
|
|
||||||
indent = indent.substring(2); //trim an indent level when a close paren is hit
|
|
||||||
}
|
|
||||||
builder.append("\n" + indent + piece.toString().trim());
|
|
||||||
if (piece.equals(Operator.OPEN_PAREN)) {
|
|
||||||
indent += " "; //add an indent level when an open paren is hit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return expression.isEmpty();
|
return model.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
expression.clear();
|
model.reset();
|
||||||
editScreen = null;
|
editScreen = null;
|
||||||
if (label != null) {
|
|
||||||
updateLabel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void buildWidget(Widget widget) {
|
protected void buildWidget(Widget widget) {
|
||||||
label = new FiltersLabel();
|
label = new FiltersLabel();
|
||||||
updateLabel();
|
model.setLabel(label);
|
||||||
widget.add(label);
|
widget.add(label);
|
||||||
widget.setVisible(!isEmpty());
|
widget.setVisible(!isEmpty());
|
||||||
}
|
}
|
||||||
@@ -215,7 +89,7 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean tap(float x, float y, int count) {
|
public boolean tap(float x, float y, int count) {
|
||||||
FTooltip tooltip = new FTooltip(getTooltip());
|
FTooltip tooltip = new FTooltip(model.getTooltip());
|
||||||
tooltip.show(this, x, getHeight());
|
tooltip.show(this, x, getHeight());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -241,48 +115,16 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
|
|
||||||
private EditScreen() {
|
private EditScreen() {
|
||||||
super("Advanced Search");
|
super("Advanced Search");
|
||||||
scroller.add(new Filter());
|
Filter filter = new Filter();
|
||||||
|
model.addFilterControl(filter);
|
||||||
|
scroller.add(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void onClose(Callback<Boolean> canCloseCallback) {
|
public void onClose(Callback<Boolean> canCloseCallback) {
|
||||||
//build expression when closing screen
|
//update expression when screen closed
|
||||||
expression.clear();
|
model.updateExpression();
|
||||||
|
|
||||||
for (FDisplayObject child : scroller.getChildren()) {
|
|
||||||
Filter filter = (Filter)child;
|
|
||||||
if (filter.filter == null) { continue; } //skip any blank filters
|
|
||||||
|
|
||||||
if (filter.btnNotBeforeParen.isSelected()) {
|
|
||||||
expression.add(Operator.NOT);
|
|
||||||
}
|
|
||||||
if (filter.btnOpenParen.isSelected()) {
|
|
||||||
expression.add(Operator.OPEN_PAREN);
|
|
||||||
}
|
|
||||||
if (filter.btnNotAfterParen.isSelected()) {
|
|
||||||
expression.add(Operator.NOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
expression.add(filter.filter);
|
|
||||||
|
|
||||||
if (filter.btnCloseParen.isSelected()) {
|
|
||||||
expression.add(Operator.CLOSE_PAREN);
|
|
||||||
}
|
|
||||||
if (filter.btnAnd.isSelected()) {
|
|
||||||
expression.add(Operator.AND);
|
|
||||||
}
|
|
||||||
else if (filter.btnOr.isSelected()) {
|
|
||||||
expression.add(Operator.OR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (label != null) {
|
|
||||||
updateLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
itemManager.applyNewOrModifiedFilter(AdvancedSearchFilter.this);
|
itemManager.applyNewOrModifiedFilter(AdvancedSearchFilter.this);
|
||||||
|
|
||||||
super.onClose(canCloseCallback);
|
super.onClose(canCloseCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +135,9 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
|
|
||||||
private void addNewFilter(Filter fromFilter) {
|
private void addNewFilter(Filter fromFilter) {
|
||||||
if (scroller.getChildAt(scroller.getChildCount() - 1) == fromFilter) {
|
if (scroller.getChildAt(scroller.getChildCount() - 1) == fromFilter) {
|
||||||
scroller.add(new Filter());
|
Filter filter = new Filter();
|
||||||
|
model.addFilterControl(filter);
|
||||||
|
scroller.add(filter);
|
||||||
scroller.revalidate();
|
scroller.revalidate();
|
||||||
scroller.scrollToBottom();
|
scroller.scrollToBottom();
|
||||||
}
|
}
|
||||||
@@ -304,14 +148,13 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
int index = scroller.indexOf(fromFilter);
|
int index = scroller.indexOf(fromFilter);
|
||||||
if (index < scroller.getChildCount() - 1) {
|
if (index < scroller.getChildCount() - 1) {
|
||||||
Filter nextFilter = (Filter)scroller.getChildAt(index + 1);
|
Filter nextFilter = (Filter)scroller.getChildAt(index + 1);
|
||||||
fromFilter.btnAnd.setSelected(nextFilter.btnAnd.isSelected());
|
model.removeFilterControl(nextFilter);
|
||||||
fromFilter.btnOr.setSelected(nextFilter.btnOr.isSelected());
|
|
||||||
scroller.remove(nextFilter);
|
scroller.remove(nextFilter);
|
||||||
scroller.revalidate();
|
scroller.revalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Filter extends FContainer {
|
private class Filter extends FContainer implements AdvancedSearch.IFilterControl<T> {
|
||||||
private final FLabel btnNotBeforeParen, btnOpenParen, btnNotAfterParen;
|
private final FLabel btnNotBeforeParen, btnOpenParen, btnNotAfterParen;
|
||||||
private final FLabel btnFilter;
|
private final FLabel btnFilter;
|
||||||
private final FLabel btnCloseParen, btnAnd, btnOr;
|
private final FLabel btnCloseParen, btnAnd, btnOr;
|
||||||
@@ -321,41 +164,7 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
btnNotBeforeParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("NOT").selectable().build());
|
btnNotBeforeParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("NOT").selectable().build());
|
||||||
btnOpenParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("(").selectable().build());
|
btnOpenParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("(").selectable().build());
|
||||||
btnNotAfterParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("NOT").selectable().build());
|
btnNotAfterParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("NOT").selectable().build());
|
||||||
|
btnFilter = add(new FLabel.ButtonBuilder().parseSymbols(true).build());
|
||||||
final String emptyFilterText = "Select Filter...";
|
|
||||||
btnFilter = add(new FLabel.ButtonBuilder().text(emptyFilterText).parseSymbols(true).command(new FEventHandler() {
|
|
||||||
@Override
|
|
||||||
public void handleEvent(FEvent e) {
|
|
||||||
FThreads.invokeInBackgroundThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final AdvancedSearch.Filter<T> newFilter = AdvancedSearch.getFilter(itemManager.getGenericType(), filter);
|
|
||||||
if (filter != newFilter) {
|
|
||||||
FThreads.invokeInEdtLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
filter = newFilter;
|
|
||||||
if (filter != null) {
|
|
||||||
btnFilter.setText(filter.toString());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
btnFilter.setText(emptyFilterText);
|
|
||||||
}
|
|
||||||
if (filter.getOption() == FilterOption.CARD_KEYWORDS) {
|
|
||||||
//the first time the user selects keywords, preload keywords for all cards
|
|
||||||
Runnable preloadTask = Keyword.getPreloadTask();
|
|
||||||
if (preloadTask != null) {
|
|
||||||
LoadingOverlay.runBackgroundTask("Loading keywords...", preloadTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).build());
|
|
||||||
|
|
||||||
btnCloseParen = add(new FLabel.Builder().align(HAlignment.CENTER).selectable().text(")").build());
|
btnCloseParen = add(new FLabel.Builder().align(HAlignment.CENTER).selectable().text(")").build());
|
||||||
btnAnd = add(new FLabel.Builder().align(HAlignment.CENTER).text("AND").selectable().command(new FEventHandler() {
|
btnAnd = add(new FLabel.Builder().align(HAlignment.CENTER).text("AND").selectable().command(new FEventHandler() {
|
||||||
@Override
|
@Override
|
||||||
@@ -409,24 +218,47 @@ public class AdvancedSearchFilter<T extends InventoryItem> extends ItemFilter<T>
|
|||||||
x += dx;
|
x += dx;
|
||||||
btnOr.setBounds(x, y, buttonWidth, buttonHeight);
|
btnOr.setBounds(x, y, buttonWidth, buttonHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IButton getBtnNotBeforeParen() {
|
||||||
|
return btnNotBeforeParen;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public IButton getBtnOpenParen() {
|
||||||
|
return btnOpenParen;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
private enum Operator {
|
public IButton getBtnNotAfterParen() {
|
||||||
AND(" AND "),
|
return btnNotAfterParen;
|
||||||
OR(" OR "),
|
}
|
||||||
NOT("NOT "),
|
@Override
|
||||||
OPEN_PAREN("("),
|
public IButton getBtnFilter() {
|
||||||
CLOSE_PAREN(")");
|
return btnFilter;
|
||||||
|
}
|
||||||
private final String token;
|
@Override
|
||||||
|
public IButton getBtnCloseParen() {
|
||||||
private Operator(String token0) {
|
return btnCloseParen;
|
||||||
token = token0;
|
}
|
||||||
|
@Override
|
||||||
|
public IButton getBtnAnd() {
|
||||||
|
return btnAnd;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public IButton getBtnOr() {
|
||||||
|
return btnOr;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public AdvancedSearch.Filter<T> getFilter() {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setFilter(AdvancedSearch.Filter<T> filter0) {
|
||||||
|
filter = filter0;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<? super T> getGenericType() {
|
||||||
|
return itemManager.getGenericType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,5 @@ public interface IGuiBase {
|
|||||||
void showBazaar();
|
void showBazaar();
|
||||||
IGuiGame getNewGuiGame();
|
IGuiGame getNewGuiGame();
|
||||||
HostedMatch hostMatch();
|
HostedMatch hostMatch();
|
||||||
|
void runBackgroundTask(String message, Runnable task);
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,11 @@ import java.util.Map.Entry;
|
|||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
|
import forge.FThreads;
|
||||||
|
import forge.GuiBase;
|
||||||
|
import forge.UiCommand;
|
||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
import forge.card.CardRarity;
|
import forge.card.CardRarity;
|
||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
@@ -18,6 +22,7 @@ import forge.deck.DeckProxy;
|
|||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
import forge.game.GameFormat;
|
import forge.game.GameFormat;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.interfaces.IButton;
|
||||||
import forge.item.InventoryItem;
|
import forge.item.InventoryItem;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import forge.model.FModel;
|
import forge.model.FModel;
|
||||||
@@ -908,4 +913,262 @@ public class AdvancedSearch {
|
|||||||
return caption;
|
return caption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IFilterControl<T extends InventoryItem> {
|
||||||
|
IButton getBtnNotBeforeParen();
|
||||||
|
IButton getBtnOpenParen();
|
||||||
|
IButton getBtnNotAfterParen();
|
||||||
|
IButton getBtnFilter();
|
||||||
|
IButton getBtnCloseParen();
|
||||||
|
IButton getBtnAnd();
|
||||||
|
IButton getBtnOr();
|
||||||
|
Filter<T> getFilter();
|
||||||
|
void setFilter(Filter<T> filter0);
|
||||||
|
Class<? super T> getGenericType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Model<T extends InventoryItem> {
|
||||||
|
private final List<Object> expression = new ArrayList<Object>();
|
||||||
|
private final List<IFilterControl<T>> controls = new ArrayList<IFilterControl<T>>();
|
||||||
|
private IButton label;
|
||||||
|
|
||||||
|
public Model() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Predicate<T> getPredicate() {
|
||||||
|
return getPredicatePiece(new ExpressionIterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Predicate<T> getPredicatePiece(ExpressionIterator iterator) {
|
||||||
|
Predicate<T> pred = null;
|
||||||
|
Predicate<T> predPiece = null;
|
||||||
|
Operator operator = null;
|
||||||
|
boolean applyNot = false;
|
||||||
|
|
||||||
|
for (; iterator.hasNext(); iterator.next()) {
|
||||||
|
Object piece = iterator.get();
|
||||||
|
if (piece.equals(Operator.OPEN_PAREN)) {
|
||||||
|
predPiece = getPredicatePiece(iterator.next());
|
||||||
|
}
|
||||||
|
else if (piece.equals(Operator.CLOSE_PAREN)) {
|
||||||
|
return pred;
|
||||||
|
}
|
||||||
|
else if (piece.equals(Operator.AND)) {
|
||||||
|
operator = Operator.AND;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (piece.equals(Operator.OR)) {
|
||||||
|
operator = Operator.OR;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (piece.equals(Operator.NOT)) {
|
||||||
|
applyNot = !applyNot;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
predPiece = ((AdvancedSearch.Filter<T>) piece).getPredicate();
|
||||||
|
}
|
||||||
|
if (applyNot) {
|
||||||
|
predPiece = Predicates.not(predPiece);
|
||||||
|
applyNot = false;
|
||||||
|
}
|
||||||
|
if (pred == null) {
|
||||||
|
pred = predPiece;
|
||||||
|
}
|
||||||
|
else if (operator == Operator.AND) {
|
||||||
|
pred = Predicates.and(pred, predPiece);
|
||||||
|
}
|
||||||
|
else if (operator == Operator.OR) {
|
||||||
|
pred = Predicates.or(pred, predPiece);
|
||||||
|
}
|
||||||
|
operator = null;
|
||||||
|
}
|
||||||
|
return pred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return expression.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
expression.clear();
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public void addFilterControl(final IFilterControl<T> control) {
|
||||||
|
final String emptyFilterText = "Select Filter...";
|
||||||
|
control.getBtnFilter().setText(emptyFilterText);
|
||||||
|
control.getBtnFilter().setCommand(new UiCommand() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FThreads.invokeInBackgroundThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Filter<T> filter = getFilter(control.getGenericType(), control.getFilter());
|
||||||
|
if (control.getFilter() != filter) {
|
||||||
|
FThreads.invokeInEdtLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
control.setFilter(filter);
|
||||||
|
if (filter != null) {
|
||||||
|
control.getBtnFilter().setText(filter.toString());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
control.getBtnFilter().setText(emptyFilterText);
|
||||||
|
}
|
||||||
|
if (filter.getOption() == FilterOption.CARD_KEYWORDS) {
|
||||||
|
//the first time the user selects keywords, preload keywords for all cards
|
||||||
|
Runnable preloadTask = Keyword.getPreloadTask();
|
||||||
|
if (preloadTask != null) {
|
||||||
|
GuiBase.getInterface().runBackgroundTask("Loading keywords...", preloadTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(control);
|
||||||
|
}
|
||||||
|
public void removeFilterControl(IFilterControl<T> control) {
|
||||||
|
int index = controls.indexOf(control);
|
||||||
|
if (index > 0) {
|
||||||
|
IFilterControl<T> prevControl = controls.get(index - 1);
|
||||||
|
prevControl.getBtnAnd().setSelected(control.getBtnAnd().isSelected());
|
||||||
|
prevControl.getBtnOr().setSelected(control.getBtnOr().isSelected());
|
||||||
|
}
|
||||||
|
controls.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabel(IButton label0) {
|
||||||
|
label = label0;
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void updateLabel() {
|
||||||
|
if (label == null) { return; }
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("Filter: ");
|
||||||
|
if (expression.isEmpty()) {
|
||||||
|
builder.append("(none)");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int prevFilterEndIdx = -1;
|
||||||
|
AdvancedSearch.Filter<T> filter, prevFilter = null;
|
||||||
|
for (Object piece : expression) {
|
||||||
|
if (piece instanceof AdvancedSearch.Filter) {
|
||||||
|
filter = (AdvancedSearch.Filter<T>)piece;
|
||||||
|
if (filter.canMergeCaptionWith(prevFilter)) {
|
||||||
|
//convert boolean operators between filters to lowercase
|
||||||
|
builder.replace(prevFilterEndIdx, builder.length(), builder.substring(prevFilterEndIdx).toLowerCase());
|
||||||
|
//append only values for filter
|
||||||
|
builder.append(filter.extractValuesFromCaption());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
builder.append(filter);
|
||||||
|
}
|
||||||
|
prevFilter = filter;
|
||||||
|
prevFilterEndIdx = builder.length();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (piece.equals(Operator.OPEN_PAREN) || piece.equals(Operator.CLOSE_PAREN)) {
|
||||||
|
prevFilter = null; //prevent merging filters with parentheses in between
|
||||||
|
}
|
||||||
|
builder.append(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label.setText(builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTooltip() {
|
||||||
|
if (expression.isEmpty()) { return ""; }
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("Filter:\n");
|
||||||
|
|
||||||
|
String indent = "";
|
||||||
|
|
||||||
|
for (Object piece : expression) {
|
||||||
|
if (piece.equals(Operator.CLOSE_PAREN) && !indent.isEmpty()) {
|
||||||
|
indent = indent.substring(2); //trim an indent level when a close paren is hit
|
||||||
|
}
|
||||||
|
builder.append("\n" + indent + piece.toString().trim());
|
||||||
|
if (piece.equals(Operator.OPEN_PAREN)) {
|
||||||
|
indent += " "; //add an indent level when an open paren is hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateExpression() {
|
||||||
|
expression.clear();
|
||||||
|
|
||||||
|
for (IFilterControl<T> control : controls) {
|
||||||
|
if (control.getFilter() == null) { continue; } //skip any blank filters
|
||||||
|
|
||||||
|
if (control.getBtnNotBeforeParen().isSelected()) {
|
||||||
|
expression.add(Operator.NOT);
|
||||||
|
}
|
||||||
|
if (control.getBtnOpenParen().isSelected()) {
|
||||||
|
expression.add(Operator.OPEN_PAREN);
|
||||||
|
}
|
||||||
|
if (control.getBtnNotAfterParen().isSelected()) {
|
||||||
|
expression.add(Operator.NOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
expression.add(control.getFilter());
|
||||||
|
|
||||||
|
if (control.getBtnCloseParen().isSelected()) {
|
||||||
|
expression.add(Operator.CLOSE_PAREN);
|
||||||
|
}
|
||||||
|
if (control.getBtnAnd().isSelected()) {
|
||||||
|
expression.add(Operator.AND);
|
||||||
|
}
|
||||||
|
else if (control.getBtnOr().isSelected()) {
|
||||||
|
expression.add(Operator.OR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExpressionIterator {
|
||||||
|
private int index;
|
||||||
|
private boolean hasNext() {
|
||||||
|
return index < expression.size();
|
||||||
|
}
|
||||||
|
private ExpressionIterator next() {
|
||||||
|
index++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
private Object get() {
|
||||||
|
return expression.get(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Operator {
|
||||||
|
AND(" AND "),
|
||||||
|
OR(" OR "),
|
||||||
|
NOT("NOT "),
|
||||||
|
OPEN_PAREN("("),
|
||||||
|
CLOSE_PAREN(")");
|
||||||
|
|
||||||
|
private final String token;
|
||||||
|
|
||||||
|
private Operator(String token0) {
|
||||||
|
token = token0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user