forge.itemmanager.ItemManager.java Source code

Java tutorial

Introduction

Here is the source code for forge.itemmanager.ItemManager.java

Source

/*
 * Forge: Play Magic: the Gathering.
 * Copyright (C) 2011  Forge Team
 *
 * This program is free software: you can redistribute it and/or modify
 * 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/>.
 */
package forge.itemmanager;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import com.badlogic.gdx.math.Rectangle;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;

import forge.FThreads;
import forge.Forge;
import forge.Graphics;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
import forge.card.CardZoom.ActivateHandler;
import forge.item.InventoryItem;
import forge.itemmanager.filters.AdvancedSearchFilter;
import forge.itemmanager.filters.ItemFilter;
import forge.itemmanager.filters.TextSearchFilter;
import forge.itemmanager.views.ImageView;
import forge.itemmanager.views.ItemListView;
import forge.itemmanager.views.ItemView;
import forge.menu.FDropDownMenu;
import forge.menu.FMenuItem;
import forge.menu.FPopupMenu;
import forge.screens.FScreen;
import forge.toolbox.FComboBox;
import forge.toolbox.FContainer;
import forge.toolbox.FEvent;
import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FEvent.FEventType;
import forge.toolbox.FLabel;
import forge.toolbox.FList.CompactModeHandler;
import forge.util.ItemPool;
import forge.util.LayoutHelper;

import java.util.*;
import java.util.Map.Entry;

public abstract class ItemManager<T extends InventoryItem> extends FContainer
        implements IItemManager<T>, ActivateHandler {
    private ItemPool<T> pool;
    protected final ItemManagerModel<T> model;
    private Predicate<? super T> filterPredicate = null;
    private AdvancedSearchFilter<? extends T> advancedSearchFilter;
    private final List<ItemFilter<? extends T>> filters = new ArrayList<ItemFilter<? extends T>>();
    private boolean hideFilters = false;
    private boolean wantUnique = false;
    private FEventHandler selectionChangedHandler, itemActivateHandler;
    private ContextMenuBuilder<T> contextMenuBuilder;
    private ContextMenu contextMenu;
    private final Class<T> genericType;
    private ItemManagerConfig config;
    private Function<Entry<? extends InventoryItem, Integer>, Object> fnNewGet;
    private boolean viewUpdating, needSecondUpdate;
    private List<ItemColumn> sortCols = new ArrayList<ItemColumn>();

    private final TextSearchFilter<? extends T> searchFilter;

    private final FLabel btnSearch = new FLabel.ButtonBuilder().icon(FSkinImage.SEARCH).iconScaleFactor(0.9f)
            .build();
    private final FLabel btnView = new FLabel.ButtonBuilder().iconScaleFactor(0.9f).build(); //icon set later
    private final FLabel btnAdvancedSearchOptions = new FLabel.Builder().selectable(true).align(HAlignment.CENTER)
            .icon(FSkinImage.SETTINGS).iconScaleFactor(0.9f).build();

    private final FComboBox<ItemColumn> cbxSortOptions;

    private final List<ItemView<T>> views = new ArrayList<ItemView<T>>();
    private final ItemListView<T> listView;
    private final ImageView<T> imageView;
    private ItemView<T> currentView;
    private boolean initialized;
    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
     */
    protected ItemManager(final Class<T> genericType0, final boolean wantUnique0) {
        genericType = genericType0;
        wantUnique = wantUnique0;
        model = new ItemManagerModel<T>(genericType0);

        searchFilter = createSearchFilter();

        listView = new ItemListView<T>(this, model);
        imageView = createImageView(model);

        views.add(listView);
        views.add(imageView);
        currentView = listView;
        btnView.setIcon(currentView.getIcon());

        //build display
        add(searchFilter.getWidget());
        add(btnSearch);
        add(btnView);
        add(btnAdvancedSearchOptions);
        btnAdvancedSearchOptions.setSelected(!hideFilters);
        if (allowSortChange()) {
            cbxSortOptions = add(new FComboBox<ItemColumn>("Sort: "));
            cbxSortOptions.setFont(FSkinFont.get(12));
        } else {
            cbxSortOptions = null;
        }
        add(currentView.getPnlOptions());
        add(currentView.getScroller());

        btnSearch.setCommand(new FEventHandler() {
            @Override
            public void handleEvent(FEvent e) {
                FPopupMenu menu = new FPopupMenu() {
                    @Override
                    protected void buildMenu() {
                        addItem(new FMenuItem("Advanced Search", FSkinImage.SEARCH, new FEventHandler() {
                            @Override
                            public void handleEvent(FEvent e) {
                                if (advancedSearchFilter == null) {
                                    advancedSearchFilter = createAdvancedSearchFilter();
                                    ItemManager.this.add(advancedSearchFilter.getWidget());
                                }
                                advancedSearchFilter.edit();
                            }
                        }));
                        addItem(new FMenuItem("Reset Filters", FSkinImage.DELETE, new FEventHandler() {
                            @Override
                            public void handleEvent(FEvent e) {
                                resetFilters();
                            }
                        }));
                    }
                };
                menu.show(btnSearch, 0, btnSearch.getHeight());
            }
        });
        btnView.setCommand(new FEventHandler() {
            @Override
            public void handleEvent(FEvent e) {
                FPopupMenu menu = new FPopupMenu() {
                    @Override
                    protected void buildMenu() {
                        for (int i = 0; i < views.size(); i++) {
                            final int index = i;
                            ItemView<T> view = views.get(i);
                            FMenuItem item = new FMenuItem(view.getCaption(), view.getIcon(), new FEventHandler() {
                                @Override
                                public void handleEvent(FEvent e) {
                                    setViewIndex(index);
                                }
                            });
                            if (currentView == view) {
                                item.setSelected(true);
                            }
                            addItem(item);
                        }
                    }
                };
                menu.show(btnView, 0, btnView.getHeight());
            }
        });
        btnAdvancedSearchOptions.setCommand(new FEventHandler() {
            @Override
            public void handleEvent(FEvent e) {
                setHideFilters(!hideFilters);
            }
        });

        //setup initial filters
        addDefaultFilters();

        initialized = true; //must set flag just before applying filters
        if (!applyFilters()) {
            if (pool != null) { //ensure view updated even if filter predicate didn't change
                updateView(true, null);
            }
        }
    }

    protected ImageView<T> createImageView(final ItemManagerModel<T> model0) {
        return new ImageView<T>(this, model0);
    }

    public ItemManagerConfig getConfig() {
        return config;
    }

    public void setup(ItemManagerConfig config0) {
        setup(config0, null);
    }

    public void setup(ItemManagerConfig config0, Map<ColumnDef, ItemColumn> colOverrides) {
        config = config0;
        setWantUnique(config0.getUniqueCardsOnly());

        //ensure sort cols ordered properly
        final List<ItemColumn> cols = new LinkedList<ItemColumn>();
        for (ItemColumnConfig colConfig : config.getCols().values()) {
            if (colOverrides == null || !colOverrides.containsKey(colConfig.getDef())) {
                cols.add(new ItemColumn(colConfig));
            } else {
                cols.add(colOverrides.get(colConfig.getDef()));
            }
        }
        Collections.sort(cols, new Comparator<ItemColumn>() {
            @Override
            public int compare(ItemColumn arg0, ItemColumn arg1) {
                return Integer.compare(arg0.getConfig().getIndex(), arg1.getConfig().getIndex());
            }
        });

        sortCols.clear();
        if (cbxSortOptions != null) {
            cbxSortOptions.setDropDownItemTap(null);
            cbxSortOptions.removeAllItems();
        }

        int modelIndex = 0;
        for (final ItemColumn col : cols) {
            col.setIndex(modelIndex++);
            if (col.isVisible()) {
                sortCols.add(col);
            }
        }

        final ItemColumn[] sortcols = new ItemColumn[sortCols.size()];

        // Assemble priority sort.
        for (ItemColumn col : sortCols) {
            if (cbxSortOptions != null) {
                cbxSortOptions.addItem(col);
            }
            if (col.getSortPriority() > 0 && col.getSortPriority() <= sortcols.length) {
                sortcols[col.getSortPriority() - 1] = col;
            }
        }

        if (cbxSortOptions != null) {
            cbxSortOptions.setText("(none)");
        }

        model.getCascadeManager().reset();

        for (int i = sortcols.length - 1; i >= 0; i--) {
            ItemColumn col = sortcols[i];
            if (col != null) {
                model.getCascadeManager().add(col, true);
                if (cbxSortOptions != null) {
                    cbxSortOptions.setSelectedItem(col);
                }
            }
        }

        if (cbxSortOptions != null) {
            cbxSortOptions.setDropDownItemTap(new FEventHandler() {
                @Override
                public void handleEvent(FEvent e) {
                    model.getCascadeManager().add((ItemColumn) e.getArgs(), false);
                    model.refreshSort();
                    ItemManagerConfig.save();
                    updateView(true, null);
                }
            });
        }

        for (ItemView<T> view : views) {
            view.setup(config0, colOverrides);
        }
        setViewIndex(config0.getViewIndex());
        setHideFilters(config0.getHideFilters());

        if (colOverrides == null || !colOverrides.containsKey(ColumnDef.NEW)) {
            fnNewGet = null;
        } else {
            fnNewGet = colOverrides.get(ColumnDef.NEW).getFnDisplay();
        }
    }

    protected boolean allowSortChange() {
        return true;
    }

    protected String getItemSuffix(Entry<T, Integer> item) {
        if (fnNewGet != null) {
            String suffix = fnNewGet.apply(item).toString();
            if (!suffix.isEmpty()) {
                return " *" + suffix + "*";
            }
        }
        return null;
    }

    public abstract class ItemRenderer {
        public abstract float getItemHeight();

        public abstract boolean tap(Integer index, Entry<T, Integer> value, float x, float y, int count);

        public abstract boolean longPress(Integer index, Entry<T, Integer> value, float x, float y);

        public abstract void drawValue(Graphics g, Entry<T, Integer> value, FSkinFont font, FSkinColor foreColor,
                FSkinColor backColor, boolean pressed, float x, float y, float w, float h);
    }

    public abstract ItemRenderer getListItemRenderer(final CompactModeHandler compactModeHandler);

    public void setViewIndex(int viewIndex) {
        if (viewIndex < 0 || viewIndex >= views.size()) {
            return;
        }
        ItemView<T> view = views.get(viewIndex);
        if (currentView == view) {
            return;
        }

        if (config != null) {
            config.setViewIndex(viewIndex);
        }

        final int backupIndexToSelect = currentView.getSelectedIndex();
        final Iterable<T> itemsToSelect; //only retain selected items if not single selection of first item
        if (backupIndexToSelect > 0 || currentView.getSelectionCount() > 1) {
            itemsToSelect = currentView.getSelectedItems();
        } else {
            itemsToSelect = null;
        }

        remove(currentView.getPnlOptions());
        remove(currentView.getScroller());

        currentView = view;
        btnView.setIcon(view.getIcon());

        view.refresh(itemsToSelect, backupIndexToSelect, 0);

        add(view.getPnlOptions());
        add(view.getScroller());
        revalidate();
    }

    @Override
    public void doLayout(float width, float height) {
        LayoutHelper helper = new LayoutHelper(this, ItemFilter.PADDING, ItemFilter.PADDING);
        float fieldHeight = searchFilter.getMainComponent().getHeight();
        float viewButtonWidth = fieldHeight;
        helper.offset(0, ItemFilter.PADDING);
        helper.fillLine(searchFilter.getWidget(), fieldHeight, (viewButtonWidth + helper.getGapX()) * 3); //leave room for search, view, and options buttons
        helper.include(btnSearch, viewButtonWidth, fieldHeight);
        helper.include(btnView, viewButtonWidth, fieldHeight);
        helper.include(btnAdvancedSearchOptions, viewButtonWidth, fieldHeight);
        helper.newLine();
        if (advancedSearchFilter != null && advancedSearchFilter.getWidget().isVisible()) {
            helper.fillLine(advancedSearchFilter.getWidget(), fieldHeight);
        }
        if (!hideFilters) {
            for (ItemFilter<? extends T> filter : filters) {
                helper.include(filter.getWidget(),
                        filter.getPreferredWidth(helper.getRemainingLineWidth(), fieldHeight), fieldHeight);
            }
            if (allowSortChange()) {
                helper.fillLine(cbxSortOptions, fieldHeight);
            }
            helper.newLine(-ItemFilter.PADDING);
            if (currentView.getPnlOptions().getChildCount() > 0) {
                helper.fillLine(currentView.getPnlOptions(), fieldHeight + ItemFilter.PADDING);
            } else {
                helper.offset(0, -fieldHeight); //prevent showing whitespace for empty view options panel
            }
        }
        helper.fill(currentView.getScroller());
    }

    /**
     * 
     * getGenericType.
     * 
     * @return generic type of items
     */
    public Class<T> getGenericType() {
        return genericType;
    }

    /**
     * 
     * getCaption.
     * 
     * @return caption to display before ratio
     */
    public String getCaption() {
        return searchFilter.getCaption();
    }

    /**
     * 
     * setCaption.
     * 
     * @param caption - caption to display before ratio
     */
    public void setCaption(String caption0) {
        searchFilter.setCaption(caption0);
    }

    /**
     * 
     * Gets the item pool.
     * 
     * @return ItemPoolView
     */
    public ItemPool<T> getPool() {
        return pool;
    }

    /**
     * 
     * Sets the item pool.
     * 
     * @param items
     */
    public void setPool(final Iterable<T> items) {
        setPool(ItemPool.createFrom(items, genericType), false);
    }

    /**
     * 
     * Sets the item pool.
     * 
     * @param poolView
     * @param infinite
     */
    public void setPool(final ItemPool<T> poolView, boolean infinite) {
        setPoolImpl(ItemPool.createFrom(poolView, genericType), infinite);
    }

    public void setPool(final ItemPool<T> pool0) {
        setPoolImpl(pool0, false);
    }

    /**
     * 
     * Sets the item pool.
     * 
     * @param pool0
     * @param infinite
     */
    private void setPoolImpl(final ItemPool<T> pool0, boolean infinite) {
        model.clear();
        pool = pool0;
        model.addItems(pool);
        model.setInfinite(infinite);
        updateView(true, null);
    }

    public ItemView<T> getCurrentView() {
        return currentView;
    }

    /**
     * 
     * getItemCount.
     * 
     * @return int
     */
    public int getItemCount() {
        return currentView.getCount();
    }

    /**
     * 
     * getSelectionCount.
     * 
     * @return int
     */
    public int getSelectionCount() {
        return currentView.getSelectionCount();
    }

    /**
     * 
     * getSelectedItem.
     * 
     * @return T
     */
    public T getSelectedItem() {
        return currentView.getSelectedItem();
    }

    /**
     * 
     * getSelectedItems.
     * 
     * @return Iterable<T>
     */
    public Collection<T> getSelectedItems() {
        return currentView.getSelectedItems();
    }

    /**
     * 
     * getSelectedItems.
     * 
     * @return ItemPool<T>
     */
    public ItemPool<T> getSelectedItemPool() {
        ItemPool<T> selectedItemPool = new ItemPool<T>(genericType);
        for (T item : getSelectedItems()) {
            selectedItemPool.add(item, getItemCount(item));
        }
        return selectedItemPool;
    }

    /**
     * 
     * setSelectedItem.
     * 
     * @param item - Item to select
     */
    public boolean setSelectedItem(T item) {
        return currentView.setSelectedItem(item);
    }

    /**
     * 
     * setSelectedItems.
     * 
     * @param items - Items to select
     */
    public boolean setSelectedItems(Iterable<T> items) {
        return currentView.setSelectedItems(items);
    }

    /**
     * 
     * stringToItem.
     * 
     * @param str - String to get item corresponding to
     */
    public T stringToItem(String str) {
        for (Entry<T, Integer> itemEntry : pool) {
            if (itemEntry.getKey().toString().equals(str)) {
                return itemEntry.getKey();
            }
        }
        return null;
    }

    /**
     * 
     * setSelectedString.
     * 
     * @param str - String to select
     */
    public boolean setSelectedString(String str) {
        T item = stringToItem(str);
        if (item != null) {
            return setSelectedItem(item);
        }
        return false;
    }

    /**
     * 
     * setSelectedStrings.
     * 
     * @param strings - Strings to select
     */
    public boolean setSelectedStrings(Iterable<String> strings) {
        List<T> items = new ArrayList<T>();
        for (String str : strings) {
            T item = stringToItem(str);
            if (item != null) {
                items.add(item);
            }
        }
        return setSelectedItems(items);
    }

    /**
     * 
     * selectItemEntrys.
     * 
     * @param itemEntrys - Item entrys to select
     */
    public boolean selectItemEntrys(Iterable<Entry<T, Integer>> itemEntrys) {
        List<T> items = new ArrayList<T>();
        for (Entry<T, Integer> itemEntry : itemEntrys) {
            items.add(itemEntry.getKey());
        }
        return setSelectedItems(items);
    }

    /**
     * 
     * selectAll.
     * 
     */
    public void selectAll() {
        currentView.selectAll();
    }

    /**
     * 
     * getSelectedItem.
     * 
     * @return T
     */
    public int getSelectedIndex() {
        return currentView.getSelectedIndex();
    }

    /**
     * 
     * getSelectedItems.
     * 
     * @return Iterable<Integer>
     */
    public Iterable<Integer> getSelectedIndices() {
        return currentView.getSelectedIndices();
    }

    /**
     * 
     * setSelectedIndex.
     * 
     * @param index - Index to select
     */
    public void setSelectedIndex(int index) {
        currentView.setSelectedIndex(index);
    }

    /**
     * 
     * setSelectedIndices.
     * 
     * @param indices - Indices to select
     */
    public void setSelectedIndices(Integer[] indices) {
        currentView.setSelectedIndices(Arrays.asList(indices));
    }

    public void setSelectedIndices(Iterable<Integer> indices) {
        currentView.setSelectedIndices(indices);
    }

    /**
     * 
     * addItem.
     * 
     * @param item
     * @param qty
     */
    public void addItem(final T item, int qty) {
        pool.add(item, qty);
        if (isUnfiltered()) {
            model.addItem(item, qty);
        }
        List<T> items = new ArrayList<T>();
        items.add(item);
        updateView(false, items);
    }

    /**
     * 
     * addItems.
     * 
     * @param itemsToAdd
     */
    public void addItems(Iterable<Entry<T, Integer>> itemsToAdd) {
        pool.addAll(itemsToAdd);
        if (isUnfiltered()) {
            model.addItems(itemsToAdd);
        }

        List<T> items = new ArrayList<T>();
        for (Map.Entry<T, Integer> item : itemsToAdd) {
            items.add(item.getKey());
        }
        updateView(false, items);
    }

    /**
     * 
     * setItems.
     * 
     * @param items
     */
    public void setItems(Iterable<Entry<T, Integer>> items) {
        pool.clear();
        model.clear();
        addItems(items);
    }

    /**
     * 
     * removeItem.
     * 
     * @param item
     * @param qty
     */
    public void removeItem(final T item, int qty) {
        final Iterable<T> itemsToSelect = currentView == listView ? getSelectedItems() : null;

        pool.remove(item, qty);
        if (isUnfiltered()) {
            model.removeItem(item, qty);
        }
        updateView(false, itemsToSelect);
    }

    /**
     * 
     * removeItems.
     * 
     * @param itemsToRemove
     */
    public void removeItems(Iterable<Map.Entry<T, Integer>> itemsToRemove) {
        final Iterable<T> itemsToSelect = currentView == listView ? getSelectedItems() : null;

        for (Map.Entry<T, Integer> item : itemsToRemove) {
            pool.remove(item.getKey(), item.getValue());
            if (isUnfiltered()) {
                model.removeItem(item.getKey(), item.getValue());
            }
        }
        updateView(false, itemsToSelect);
    }

    /**
     * 
     * removeAllItems.
     * 
     */
    public void removeAllItems() {
        pool.clear();
        model.clear();
        updateView(false, null);
    }

    public void replaceAll(final T item, final T replacement) {
        int count = pool.count(item);
        if (count == 0) {
            return;
        }

        final Iterable<T> itemsToSelect = currentView == listView ? getSelectedItems() : null;

        pool.removeAll(item);
        pool.add(replacement, count);
        if (isUnfiltered()) {
            model.replaceAll(item, replacement);
        }
        updateView(false, itemsToSelect);
    }

    /**
     * 
     * scrollSelectionIntoView.
     * 
     */
    public void scrollSelectionIntoView() {
        currentView.scrollSelectionIntoView();
    }

    /**
     * 
     * getItemCount.
     * 
     * @param item
     */
    public int getItemCount(final T item) {
        return model.isInfinite() ? Integer.MAX_VALUE : pool.count(item);
    }

    /**
     * Gets all filtered items in the model.
     * 
     * @return ItemPoolView<T>
     */
    public ItemPool<T> getFilteredItems() {
        return model.getItems();
    }

    protected abstract void addDefaultFilters();

    protected abstract TextSearchFilter<? extends T> createSearchFilter();

    protected abstract AdvancedSearchFilter<? extends T> createAdvancedSearchFilter();

    public void addFilter(final ItemFilter<? extends T> filter) {
        filters.add(filter);
        add(filter.getWidget());

        boolean visible = !hideFilters;
        filter.getWidget().setVisible(visible);
        if (visible && initialized) {
            revalidate();
            applyNewOrModifiedFilter(filter);
        }
    }

    //apply filters and focus existing filter's main component if filtering not locked
    public void applyNewOrModifiedFilter(final ItemFilter<? extends T> filter) {
        if (lockFiltering) {
            return;
        }

        if (filter == advancedSearchFilter) {
            //handle update the visibility of the advanced search filter
            boolean empty = filter.isEmpty();
            ItemFilter<? extends T>.Widget widget = filter.getWidget();
            if (widget.isVisible() == empty) {
                widget.setVisible(!empty);
                revalidate();
            }
        }

        applyFilters();
    }

    public void restoreDefaultFilters() {
        lockFiltering = true;
        for (ItemFilter<? extends T> filter : filters) {
            remove(filter.getWidget());
        }
        filters.clear();
        addDefaultFilters();
        lockFiltering = false;
        revalidate();
        applyFilters();
    }

    public void resetFilters() {
        lockFiltering = true; //prevent updating filtering from this change until all filters reset
        for (final ItemFilter<? extends T> filter : filters) {
            filter.reset();
        }
        searchFilter.reset();
        if (advancedSearchFilter != null) {
            advancedSearchFilter.reset();
            ItemFilter<? extends T>.Widget widget = advancedSearchFilter.getWidget();
            if (widget.isVisible()) {
                widget.setVisible(false);
                revalidate();
            }
        }
        lockFiltering = false;

        applyFilters();
    }

    public void removeFilter(ItemFilter<? extends T> filter) {
        filters.remove(filter);
        remove(filter.getWidget());
        revalidate();
        applyFilters();
    }

    public boolean applyFilters() {
        if (lockFiltering || !initialized) {
            return false;
        }

        List<Predicate<? super T>> predicates = new ArrayList<Predicate<? super T>>();
        for (ItemFilter<? extends T> filter : filters) {
            if (!filter.isEmpty()) {
                predicates.add(filter.buildPredicate(genericType));
            }
        }
        if (!searchFilter.isEmpty()) {
            predicates.add(searchFilter.buildPredicate(genericType));
        }
        if (advancedSearchFilter != null && !advancedSearchFilter.isEmpty()) {
            predicates.add(advancedSearchFilter.buildPredicate(genericType));
        }

        Predicate<? super T> newFilterPredicate = predicates.size() == 0 ? null : Predicates.and(predicates);
        if (filterPredicate == newFilterPredicate) {
            return false;
        }

        filterPredicate = newFilterPredicate;
        if (pool != null) {
            if (viewUpdating) {
                needSecondUpdate = true;
            } else {
                viewUpdating = true;
                FThreads.invokeInBackgroundThread(new Runnable() {
                    @Override
                    public void run() {
                        do {
                            needSecondUpdate = false;
                            updateView(true, null);
                            Gdx.graphics.requestRendering();
                        } while (needSecondUpdate);
                        viewUpdating = false;
                    }
                });
            }
        }
        return true;
    }

    /**
     * 
     * isUnfiltered.
     * 
     */
    private boolean isUnfiltered() {
        return filterPredicate == null;
    }

    /**
     * 
     * getHideFilters.
     * 
     * @return true if filters are hidden, false otherwise
     */
    public boolean getHideFilters() {
        return hideFilters;
    }

    /**
     * 
     * setHideFilters.
     * 
     * @param hideFilters0 - if true, hide the filters, otherwise show them
     */
    public void setHideFilters(boolean hideFilters0) {
        if (hideFilters == hideFilters0) {
            return;
        }
        hideFilters = hideFilters0;

        boolean visible = !hideFilters0;
        for (ItemFilter<? extends T> filter : filters) {
            filter.getWidget().setVisible(visible);
        }
        if (allowSortChange()) {
            cbxSortOptions.setVisible(visible);
        }
        for (ItemView<T> view : views) {
            view.getPnlOptions().setVisible(visible);
        }

        if (initialized) {
            btnAdvancedSearchOptions.setSelected(visible);

            revalidate();

            if (config != null) {
                config.setHideFilters(hideFilters0);
            }
        }
    }

    /**
     * Refresh displayed items
     */
    public void refresh() {
        updateView(true, getSelectedItems());
    }

    /**
     * 
     * updateView.
     * 
     * @param bForceFilter
     */
    public void updateView(final boolean forceFilter, final Iterable<T> itemsToSelect) {
        final boolean useFilter = (forceFilter && (filterPredicate != null)) || !isUnfiltered();

        if (useFilter || forceFilter) {
            model.clear();

            Iterable<Entry<T, Integer>> items = pool;
            if (useFilter) {
                Predicate<Entry<T, Integer>> pred = Predicates.compose(filterPredicate, pool.FN_GET_KEY);
                items = Iterables.filter(pool, pred);
            }
            model.addItems(items);
        }

        currentView.refresh(itemsToSelect, getSelectedIndex(), forceFilter ? 0 : currentView.getScrollValue());

        //update ratio of # in filtered pool / # in total pool
        int totalCount;
        int filteredCount = getFilteredItems().countAll();
        if (useFilter) {
            totalCount = pool.countAll();
        } else {
            totalCount = filteredCount;
        }
        searchFilter.setRatio("(" + filteredCount + " / " + totalCount + ")");
    }

    /**
     * 
     * isIncrementalSearchActive.
     * 
     * @return true if an incremental search is currently active
     */
    public boolean isIncrementalSearchActive() {
        return currentView.isIncrementalSearchActive();
    }

    /**
     * 
     * getWantUnique.
     * 
     * @return true if the editor is in "unique item names only" mode.
     */
    public boolean getWantUnique() {
        return wantUnique;
    }

    /**
     * 
     * setWantUnique.
     * 
     * @param unique - if true, the editor will be set to the "unique item names only" mode.
     */
    public void setWantUnique(boolean unique) {
        wantUnique = unique;
    }

    public void setSelectionSupport(int minSelections0, int maxSelections0) {
        for (ItemView<T> view : views) {
            view.setSelectionSupport(minSelections0, maxSelections0);
        }
    }

    /**
     * 
     * isInfinite.
     * 
     * @return whether item manager's pool of items is in infinite supply
     */
    public boolean isInfinite() {
        return model.isInfinite();
    }

    public void focusSearch() {
        setHideFilters(false); //ensure filters shown
    }

    public FEventHandler getSelectionChangedHandler() {
        return selectionChangedHandler;
    }

    public void setSelectionChangedHandler(FEventHandler selectionChangedHandler0) {
        selectionChangedHandler = selectionChangedHandler0;
    }

    public void setItemActivateHandler(FEventHandler itemActivateHandler0) {
        itemActivateHandler = itemActivateHandler0;
    }

    public void activateSelectedItems() {
        if (itemActivateHandler != null) {
            itemActivateHandler.handleEvent(new FEvent(this, FEventType.ACTIVATE));
        }
    }

    public void setContextMenuBuilder(ContextMenuBuilder<T> contextMenuBuilder0) {
        contextMenuBuilder = contextMenuBuilder0;
    }

    public void showMenu(boolean delay) {
        if (contextMenuBuilder != null && getSelectionCount() > 0) {
            if (contextMenu == null) {
                contextMenu = new ContextMenu();
            }
            if (delay) { //delay showing menu to prevent it hiding right away
                FThreads.delayInEDT(50, new Runnable() {
                    @Override
                    public void run() {
                        contextMenu.show();
                        Gdx.graphics.requestRendering();
                    }
                });
            } else {
                contextMenu.show();
            }
        }
    }

    public boolean isContextMenuOpen() {
        return contextMenu != null && contextMenu.isVisible();
    }

    public static abstract class ContextMenuBuilder<T> {
        public abstract void buildMenu(final FDropDownMenu menu, final T item);
    }

    private class ContextMenu extends FDropDownMenu {
        @Override
        protected void buildMenu() {
            contextMenuBuilder.buildMenu(this, getSelectedItem());
        }

        @Override
        protected boolean hideBackdropOnPress(float x, float y) {
            Rectangle bounds = currentView.getSelectionBounds();
            if (bounds == null || bounds.contains(x, y)) {
                return false; //don't hide on press if within selection bounds
            }
            return true;
        }

        @Override
        protected boolean preventOwnerHandlingBackupTap(float x, float y, int count) {
            //prevent view handling single tap, but allow it to handle double tap
            return count == 1;
        }

        @Override
        protected void updateSizeAndPosition() {
            FScreen screen = Forge.getCurrentScreen();
            float screenWidth = screen.getWidth();
            float screenHeight = screen.getHeight();

            paneSize = updateAndGetPaneSize(screenWidth, screenHeight);
            float w = paneSize.getWidth();
            float h = paneSize.getHeight();

            Rectangle bounds = currentView.getSelectionBounds();

            //try displaying right of selection if possible
            float x = bounds.x + bounds.width;
            float y = bounds.y;
            if (x + w > screenWidth) {
                //try displaying left of selection if possible
                x = bounds.x - w;
                if (x < 0) {
                    //display below selection if no room left or right of selection
                    x = bounds.x;
                    if (w < bounds.width) {
                        //center below item if needed
                        x += (bounds.width - w) / 2;
                    }
                    if (x + w > screenWidth) {
                        x = screenWidth - w;
                    }
                    y += bounds.height;
                }
            }
            if (y + h > screenHeight) {
                if (y == bounds.y) {
                    //if displaying to left or right, move up if not enough room
                    y = screenHeight - h;
                } else {
                    //if displaying below selection and not enough room, display above selection
                    y -= bounds.height + h;
                }
                if (y < 0) {
                    y = 0;
                    if (h > bounds.y) {
                        h = bounds.y; //cut off menu if not enough room above or below selection
                    }
                }
            }

            setBounds(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
        }
    }

    @Override
    public String getActivateAction(int index) {
        if (contextMenuBuilder != null) {
            return "select card";
        }
        return null;
    }

    @Override
    public void activate(int index) {
        setSelectedIndex(index);
        showMenu(true);
    }

    public float getPileByWidth() {
        if (cbxSortOptions != null) {
            return cbxSortOptions.getWidth();
        }
        return filters.get(filters.size() - 1).getWidget().getWidth();
    }
}