diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index 556b7e662d5..95f3b797e9e 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -273,4 +273,9 @@ public class GuiDesktop implements IGuiBase { return match; } + @Override + public void runBackgroundTask(String message, Runnable task) { + //TODO: Show loading overlay + FThreads.invokeInBackgroundThread(task); + } } \ No newline at end of file diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index c2b31cd0ca9..e883cf863d5 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -27,6 +27,7 @@ import forge.interfaces.IGuiGame; import forge.item.PaperCard; import forge.match.HostedMatch; import forge.properties.ForgeConstants; +import forge.screens.LoadingOverlay; import forge.screens.match.MatchController; import forge.screens.quest.QuestMenu; import forge.screens.settings.GuiDownloader; @@ -279,4 +280,9 @@ public class GuiMobile implements IGuiBase { public HostedMatch hostMatch() { return MatchController.hostMatch(); } + + @Override + public void runBackgroundTask(String message, Runnable task) { + LoadingOverlay.runBackgroundTask(message, task); + } } diff --git a/forge-gui-mobile/src/forge/itemmanager/filters/AdvancedSearchFilter.java b/forge-gui-mobile/src/forge/itemmanager/filters/AdvancedSearchFilter.java index cd569343374..8df2d2ac273 100644 --- a/forge-gui-mobile/src/forge/itemmanager/filters/AdvancedSearchFilter.java +++ b/forge-gui-mobile/src/forge/itemmanager/filters/AdvancedSearchFilter.java @@ -1,22 +1,16 @@ package forge.itemmanager.filters; -import java.util.ArrayList; -import java.util.List; - import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import forge.FThreads; import forge.Forge; -import forge.game.keyword.Keyword; +import forge.interfaces.IButton; import forge.item.InventoryItem; import forge.itemmanager.AdvancedSearch; import forge.itemmanager.ItemManager; -import forge.itemmanager.AdvancedSearch.FilterOption; import forge.menu.FTooltip; import forge.screens.FScreen; -import forge.screens.LoadingOverlay; import forge.toolbox.FContainer; import forge.toolbox.FDisplayObject; import forge.toolbox.FEvent; @@ -29,13 +23,14 @@ import forge.util.Callback; public class AdvancedSearchFilter extends ItemFilter { - private final List expression = new ArrayList(); + private final AdvancedSearch.Model model; private FiltersLabel label; private EditScreen editScreen; public AdvancedSearchFilter(ItemManager itemManager0) { super(itemManager0); + model = new AdvancedSearch.Model(); } @Override @@ -46,76 +41,14 @@ public class AdvancedSearchFilter extends ItemFilter @Override protected final Predicate buildPredicate() { - if (expression.isEmpty()) { + if (model.isEmpty()) { return Predicates.alwaysTrue(); } - return getPredicate(); + return model.getPredicate(); } - public Predicate getPredicate() { - return getPredicatePiece(new ExpressionIterator()); - } - - 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 getPredicatePiece(ExpressionIterator iterator) { - Predicate pred = null; - Predicate 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) 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 Predicate getPredicate() { + return model.getPredicate(); } public void edit() { @@ -125,80 +58,21 @@ public class AdvancedSearchFilter extends ItemFilter 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 filter, prevFilter = null; - for (Object piece : expression) { - if (piece instanceof AdvancedSearch.Filter) { - filter = (AdvancedSearch.Filter)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 public boolean isEmpty() { - return expression.isEmpty(); + return model.isEmpty(); } @Override public void reset() { - expression.clear(); + model.reset(); editScreen = null; - if (label != null) { - updateLabel(); - } } @Override protected void buildWidget(Widget widget) { label = new FiltersLabel(); - updateLabel(); + model.setLabel(label); widget.add(label); widget.setVisible(!isEmpty()); } @@ -215,7 +89,7 @@ public class AdvancedSearchFilter extends ItemFilter @Override 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()); return true; } @@ -241,48 +115,16 @@ public class AdvancedSearchFilter extends ItemFilter private EditScreen() { super("Advanced Search"); - scroller.add(new Filter()); + Filter filter = new Filter(); + model.addFilterControl(filter); + scroller.add(filter); } @Override - @SuppressWarnings("unchecked") public void onClose(Callback canCloseCallback) { - //build expression when closing screen - expression.clear(); - - 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(); - } - + //update expression when screen closed + model.updateExpression(); itemManager.applyNewOrModifiedFilter(AdvancedSearchFilter.this); - super.onClose(canCloseCallback); } @@ -293,7 +135,9 @@ public class AdvancedSearchFilter extends ItemFilter private void addNewFilter(Filter 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.scrollToBottom(); } @@ -304,14 +148,13 @@ public class AdvancedSearchFilter extends ItemFilter int index = scroller.indexOf(fromFilter); if (index < scroller.getChildCount() - 1) { Filter nextFilter = (Filter)scroller.getChildAt(index + 1); - fromFilter.btnAnd.setSelected(nextFilter.btnAnd.isSelected()); - fromFilter.btnOr.setSelected(nextFilter.btnOr.isSelected()); + model.removeFilterControl(nextFilter); scroller.remove(nextFilter); scroller.revalidate(); } } - private class Filter extends FContainer { + private class Filter extends FContainer implements AdvancedSearch.IFilterControl { private final FLabel btnNotBeforeParen, btnOpenParen, btnNotAfterParen; private final FLabel btnFilter; private final FLabel btnCloseParen, btnAnd, btnOr; @@ -321,41 +164,7 @@ public class AdvancedSearchFilter extends ItemFilter btnNotBeforeParen = add(new FLabel.Builder().align(HAlignment.CENTER).text("NOT").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()); - - 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 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()); - + btnFilter = add(new FLabel.ButtonBuilder().parseSymbols(true).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() { @Override @@ -409,24 +218,47 @@ public class AdvancedSearchFilter extends ItemFilter x += dx; btnOr.setBounds(x, y, buttonWidth, buttonHeight); } - } - } - 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; + @Override + public IButton getBtnNotBeforeParen() { + return btnNotBeforeParen; + } + @Override + public IButton getBtnOpenParen() { + return btnOpenParen; + } + @Override + public IButton getBtnNotAfterParen() { + return btnNotAfterParen; + } + @Override + public IButton getBtnFilter() { + return btnFilter; + } + @Override + public IButton getBtnCloseParen() { + return btnCloseParen; + } + @Override + public IButton getBtnAnd() { + return btnAnd; + } + @Override + public IButton getBtnOr() { + return btnOr; + } + @Override + public AdvancedSearch.Filter getFilter() { + return filter; + } + @Override + public void setFilter(AdvancedSearch.Filter filter0) { + filter = filter0; + } + @Override + public Class getGenericType() { + return itemManager.getGenericType(); + } } } } diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java index 24d9fb3aa0e..75591369c0d 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java @@ -51,4 +51,5 @@ public interface IGuiBase { void showBazaar(); IGuiGame getNewGuiGame(); HostedMatch hostMatch(); + void runBackgroundTask(String message, Runnable task); } \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java b/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java index 92ee835d23a..0ae9123d325 100644 --- a/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java +++ b/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java @@ -5,7 +5,11 @@ import java.util.Map.Entry; import com.google.common.base.Function; 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.CardRarity; import forge.card.CardRules; @@ -18,6 +22,7 @@ import forge.deck.DeckProxy; import forge.deck.DeckSection; import forge.game.GameFormat; import forge.game.keyword.Keyword; +import forge.interfaces.IButton; import forge.item.InventoryItem; import forge.item.PaperCard; import forge.model.FModel; @@ -908,4 +913,262 @@ public class AdvancedSearch { return caption; } } + + public interface IFilterControl { + IButton getBtnNotBeforeParen(); + IButton getBtnOpenParen(); + IButton getBtnNotAfterParen(); + IButton getBtnFilter(); + IButton getBtnCloseParen(); + IButton getBtnAnd(); + IButton getBtnOr(); + Filter getFilter(); + void setFilter(Filter filter0); + Class getGenericType(); + } + + public static class Model { + private final List expression = new ArrayList(); + private final List> controls = new ArrayList>(); + private IButton label; + + public Model() { + } + + public Predicate getPredicate() { + return getPredicatePiece(new ExpressionIterator()); + } + + @SuppressWarnings("unchecked") + private Predicate getPredicatePiece(ExpressionIterator iterator) { + Predicate pred = null; + Predicate 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) 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 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 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 control) { + int index = controls.indexOf(control); + if (index > 0) { + IFilterControl 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 filter, prevFilter = null; + for (Object piece : expression) { + if (piece instanceof AdvancedSearch.Filter) { + filter = (AdvancedSearch.Filter)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 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; + } + } + } }