Java tutorial
/* * 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.toolbox; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import forge.FThreads; import forge.Graphics; import forge.assets.FSkinFont; import forge.assets.FSkinImage; import forge.item.InventoryItem; import forge.itemmanager.filters.AdvancedSearchFilter; import forge.itemmanager.filters.ItemFilter; import forge.itemmanager.filters.ListLabelFilter; import forge.menu.FMenuItem; import forge.menu.FPopupMenu; import forge.toolbox.FEvent; import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FOptionPane; import forge.util.Callback; import forge.util.Utils; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * A simple class that shows a list of choices in a dialog. Two properties * influence the behavior of a list chooser: minSelection and maxSelection. * These two give the allowed number of selected items for the dialog to be * closed. A negative value for minSelection suggests that the list is revealed * and the choice doesn't matter. * <ul> * <li>If minSelection is 0, there will be a Cancel button.</li> * <li>If minSelection is -1, 0 or 1, double-clicking a choice will also close the * dialog.</li> * <li>If the number of selections is out of bounds, the "OK" button is * disabled.</li> * <li>The dialog was "committed" if "OK" was clicked or a choice was double * clicked.</li> * <li>The dialog was "canceled" if "Cancel" or "X" was clicked.</li> * <li>If the dialog was canceled, the selection will be empty.</li> * <li> * </ul> * * @param <T> * the generic type * @author Forge * @version $Id: ListChooser.java 25183 2014-03-14 23:09:45Z drdev $ */ public class ListChooser<T> extends FContainer { // Data and number of choices for the list // Flag: was the dialog already shown? private boolean called; // initialized before; listeners may be added to it private FTextField txtSearch; private FLabel btnSearch; private ChoiceList lstChoices; private FOptionPane optionPane; private final Collection<T> list; private final Function<T, String> display; private final Callback<List<T>> callback; private AdvancedSearchFilter<? extends InventoryItem> advancedSearchFilter; public ListChooser(final String title, final int minChoices, final int maxChoices, final Collection<T> list0, final Function<T, String> display0, final Callback<List<T>> callback0) { FThreads.assertExecutedByEdt(true); list = list0; lstChoices = add(new ChoiceList(list, minChoices, maxChoices)); display = display0; callback = callback0; //only show search field if more than 25 items and vertical layout if (list.size() > 25 && !lstChoices.getListItemRenderer().layoutHorizontal()) { txtSearch = add(new FTextField()); txtSearch.setFont(FSkinFont.get(12)); txtSearch.setGhostText("Search"); txtSearch.setChangedHandler(new FEventHandler() { @Override public void handleEvent(FEvent e) { applyFilters(); } }); advancedSearchFilter = lstChoices.getListItemRenderer().getAdvancedSearchFilter(this); if (advancedSearchFilter != null) { btnSearch = add(new FLabel.ButtonBuilder().icon(FSkinImage.SEARCH).iconScaleFactor(0.9f) .command(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) { advancedSearchFilter.edit(); } })); addItem(new FMenuItem("Reset Filters", FSkinImage.DELETE, new FEventHandler() { @Override public void handleEvent(FEvent e) { resetFilters(); } })); } }; menu.show(btnSearch, 0, btnSearch.getHeight()); } }).build()); add(advancedSearchFilter.getWidget()); } } final List<String> options; if (minChoices == 0) { options = ImmutableList.of("OK", "Cancel"); } else { options = ImmutableList.of("OK"); } updateHeight(); optionPane = new FOptionPane(null, title, null, this, options, 0, new Callback<Integer>() { @Override public void run(Integer result) { called = false; if (result == 0) { callback.run(lstChoices.getSelectedItems()); } else if (minChoices > 0) { show(); //show if user tries to cancel when input is mandatory } else { callback.run(new ArrayList<T>()); } } }) { @Override protected boolean padAboveAndBelow() { return false; //allow list to go straight up against buttons } }; } public void resetFilters() { txtSearch.setText(""); if (advancedSearchFilter != null) { advancedSearchFilter.reset(); ItemFilter<? extends InventoryItem>.Widget widget = advancedSearchFilter.getWidget(); if (widget.isVisible()) { widget.setVisible(false); revalidate(); } } applyFilters(); } @SuppressWarnings("unchecked") public void applyFilters() { lstChoices.clearSelection(); List<Predicate<? super T>> predicates = new ArrayList<Predicate<? super T>>(); final String pattern = txtSearch.getText().toLowerCase(); if (!pattern.isEmpty()) { predicates.add(new Predicate<T>() { @Override public boolean apply(T input) { return lstChoices.getChoiceText(input).toLowerCase().contains(pattern); } }); } if (advancedSearchFilter != null && !advancedSearchFilter.isEmpty()) { predicates.add((Predicate<? super T>) advancedSearchFilter.getPredicate()); } if (predicates.isEmpty()) { lstChoices.setListData(list); } else { lstChoices.setListData(Iterables.filter(list, Predicates.and(predicates))); } if (!lstChoices.isEmpty() && lstChoices.getMaxChoices() > 0) { lstChoices.addSelectedIndex(0); } lstChoices.setScrollTop(0); } private void updateHeight() { boolean needRevalidate = getHeight() > 0; //needs to revalidate if already has height if (lstChoices.getListItemRenderer().layoutHorizontal()) { setHeight(Utils.AVG_FINGER_HEIGHT); } else { setHeight(Math.min(lstChoices.getListItemRenderer().getItemHeight() * list.size(), FOptionPane.getMaxDisplayObjHeight())); } if (needRevalidate) { optionPane.revalidate(); } } public void show() { show(null, false); } /** * Shows the dialog and returns after the dialog was closed. * * @param index0 index to select when shown * @return a boolean. */ public void show(final T item, final boolean selectMax) { if (called) { throw new IllegalStateException("Already shown"); } called = true; if (item == null) { if (selectMax) { lstChoices.clearSelection(); int max = Math.min(lstChoices.getMaxChoices(), list.size()); for (int i = 0; i < max; i++) { lstChoices.addSelectedIndex(i); } } else if (lstChoices.getMaxChoices() == 1) { //select first item only if single-select lstChoices.setSelectedIndex(0); } else { lstChoices.clearSelection(); } } else { lstChoices.setSelectedItem(item); } optionPane.show(); } @Override protected void doLayout(float width, float height) { float y = 0; if (txtSearch != null) { float fieldWidth = width; float fieldHeight = txtSearch.getHeight(); float padding = fieldHeight * 0.25f; y += padding; if (btnSearch != null) { float buttonWidth = fieldHeight; btnSearch.setBounds(width - buttonWidth, y, buttonWidth, fieldHeight); fieldWidth -= buttonWidth + ItemFilter.PADDING; } txtSearch.setBounds(0, y, fieldWidth, fieldHeight); if (advancedSearchFilter != null && advancedSearchFilter.getWidget().isVisible()) { padding = ItemFilter.PADDING; y += fieldHeight + padding; fieldHeight = FTextField.getDefaultHeight(ListLabelFilter.LABEL_FONT); advancedSearchFilter.getWidget().setBounds(0, y, width, fieldHeight); } y += fieldHeight + padding; } lstChoices.setBounds(0, y, width, height - y); } private class ChoiceList extends FChoiceList<T> { private ChoiceList(Collection<T> items, int minChoices0, int maxChoices0) { super(items, minChoices0, maxChoices0); } @Override protected String getChoiceText(T choice) { if (display == null) { return choice.toString(); } return display.apply(choice); } @Override protected void onSelectionChange() { final int num = getSelectionCount(); optionPane.setButtonEnabled(0, (num >= minChoices) && (num <= maxChoices || maxChoices == -1)); } @Override protected void onItemActivate(Integer index, T value) { if (optionPane.isButtonEnabled(0)) { optionPane.setResult(0); } } @Override protected void onCompactModeChange() { updateHeight(); //update height and scroll bounds based on compact mode change } @Override public void drawOverlay(Graphics g) { //don't draw border } } }