Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 102 additions & 46 deletions forge-gui-desktop/src/main/java/forge/gui/ListChooser.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import javax.swing.AbstractListModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
Expand All @@ -50,6 +53,7 @@

import forge.card.CardType;
import forge.card.MagicColor;
import forge.game.card.CardView;
import forge.game.card.CounterKeywordType;
import forge.game.card.CounterType;
import forge.localinstance.skin.FSkinProp;
Expand All @@ -59,6 +63,7 @@
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.toolbox.FTextField;
import forge.toolbox.special.CardImageGrid;
import forge.util.ITranslatable;
import forge.util.Localizer;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -88,6 +93,11 @@
* @version $Id: ListChooser.java 25183 2014-03-14 23:09:45Z drdev $
*/
public class ListChooser<T> {
private static final int GRID_THUMB_W = 200;
private static final int GRID_THUMB_H = 280;
private static final int GRID_MAX_COLUMNS = 4;
private static final int GRID_MAX_VISIBLE_ROWS = 3;

// Data and number of choices for the list
private final List<T> allItems;
private List<T> displayedItems;
Expand All @@ -101,6 +111,8 @@ public class ListChooser<T> {
private final FList<T> lstChoices;
private final FOptionPane optionPane;
private final ChooserListModel listModel;
// Non-null only in card-grid mode; keeps a typed handle for selection-restore in show()
private final CardImageGrid<CardView> cardGrid;

public ListChooser(final String title, final int minChoices, final int maxChoices, final Collection<T> list, final Function<T, String> display) {
FThreads.assertExecutedByEdt(true);
Expand All @@ -110,7 +122,6 @@ public ListChooser(final String title, final int minChoices, final int maxChoice
this.allItems = list.getClass().isInstance(List.class) ? (List<T>)list : Lists.newArrayList(list);
this.displayedItems = new ArrayList<>(this.allItems);
this.listModel = new ChooserListModel();
this.lstChoices = new FList<>(this.listModel);

final ImmutableList<String> options;
if (minChoices == 0) {
Expand All @@ -119,56 +130,76 @@ public ListChooser(final String title, final int minChoices, final int maxChoice
options = ImmutableList.of(Localizer.getInstance().getMessage("lblOK"));
}

if (maxChoices == 1 || minChoices == -1) {
this.lstChoices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}

this.lstChoices.setCellRenderer(new TransformedCellRenderer(display));
final JComponent body;
// Tracks the focus target for setDefaultFocus below. Text-mode large lists focus the search
// field so the user can start typing immediately; otherwise focus the list / grid itself.
final JComponent defaultFocus;
if (isCardChoiceView(allItems, maxChoices)) {
final int cols = Math.max(1, Math.min(allItems.size(), GRID_MAX_COLUMNS));
this.cardGrid = CardImageGrid.forCardViews(cols, GRID_THUMB_W, GRID_THUMB_H);
@SuppressWarnings("unchecked")
final List<CardView> cardItems = (List<CardView>) allItems;
this.cardGrid.setItems(cardItems);
@SuppressWarnings("unchecked")
final FList<T> gridList = (FList<T>) (FList<?>) this.cardGrid.getList();
this.lstChoices = gridList;
body = cardGrid.makeFixedSizeContainer(GRID_MAX_VISIBLE_ROWS);
defaultFocus = this.lstChoices;
} else {
this.cardGrid = null;
this.lstChoices = new FList<>(this.listModel);

final FScrollPane listScroller = new FScrollPane(this.lstChoices, true);
int minWidth = this.lstChoices.getAutoSizeWidth();
if (this.lstChoices.getModel().getSize() > this.lstChoices.getVisibleRowCount()) {
minWidth += listScroller.getVerticalScrollBar().getPreferredSize().width;
}
listScroller.setMinimumSize(new Dimension(minWidth, listScroller.getMinimumSize().height));

// Add search field for large lists (same threshold as mobile)
if (allItems.size() > 25) {
final FTextField searchField = new FTextField.Builder()
.ghostText(Localizer.getInstance().getMessage("lblSearch"))
.showGhostTextWithFocus()
.build();
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { applyFilter(searchField); }
@Override public void removeUpdate(DocumentEvent e) { applyFilter(searchField); }
@Override public void changedUpdate(DocumentEvent e) { applyFilter(searchField); }
});
searchField.addKeyListener(new KeyAdapter() {
@Override public void keyPressed(final KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
ListChooser.this.commit();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
lstChoices.requestFocusInWindow();
}
}
});
if (maxChoices == 1 || minChoices == -1) {
this.lstChoices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}

final JPanel panel = new JPanel(new BorderLayout(0, 4));
panel.setOpaque(false);
panel.add(searchField, BorderLayout.NORTH);
panel.add(listScroller, BorderLayout.CENTER);
this.lstChoices.setCellRenderer(new TransformedCellRenderer(display));

this.optionPane = new FOptionPane(null, title, null, panel, options, minChoices < 0 ? 0 : -1);
if (minChoices != -1) {
this.optionPane.setDefaultFocus(searchField);
final FScrollPane listScroller = new FScrollPane(this.lstChoices, true);
int minWidth = this.lstChoices.getAutoSizeWidth();
if (this.lstChoices.getModel().getSize() > this.lstChoices.getVisibleRowCount()) {
minWidth += listScroller.getVerticalScrollBar().getPreferredSize().width;
}
} else {
this.optionPane = new FOptionPane(null, title, null, listScroller, options, minChoices < 0 ? 0 : -1);
if (minChoices != -1) {
this.optionPane.setDefaultFocus(this.lstChoices);
listScroller.setMinimumSize(new Dimension(minWidth, listScroller.getMinimumSize().height));

// Add search field for large lists (same threshold as mobile)
if (allItems.size() > 25) {
final FTextField searchField = new FTextField.Builder()
.ghostText(Localizer.getInstance().getMessage("lblSearch"))
.showGhostTextWithFocus()
.build();
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { applyFilter(searchField); }
@Override public void removeUpdate(DocumentEvent e) { applyFilter(searchField); }
@Override public void changedUpdate(DocumentEvent e) { applyFilter(searchField); }
});
searchField.addKeyListener(new KeyAdapter() {
@Override public void keyPressed(final KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
ListChooser.this.commit();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
lstChoices.requestFocusInWindow();
}
}
});

final JPanel panel = new JPanel(new BorderLayout(0, 4));
panel.setOpaque(false);
panel.add(searchField, BorderLayout.NORTH);
panel.add(listScroller, BorderLayout.CENTER);
body = panel;
defaultFocus = searchField;
} else {
body = listScroller;
defaultFocus = this.lstChoices;
}
}

this.optionPane = new FOptionPane(null, title, null, body, options, minChoices < 0 ? 0 : -1);
if (minChoices != -1) {
this.optionPane.setDefaultFocus(defaultFocus);
}

this.optionPane.setButtonEnabled(0, minChoices <= 0);

if (minChoices > 0) {
Expand All @@ -193,6 +224,18 @@ public ListChooser(final String title, final int minChoices, final int maxChoice
});
}

private static boolean isCardChoiceView(Collection<?> list, int maxChoices) {
if (list.isEmpty() || maxChoices > 1) {
return false;
}
for (Object o : list) {
if (!(o instanceof CardView)) {
return false;
}
}
return true;
}

private void applyFilter(final FTextField searchField) {
final String text = normalize(searchField.getText());
lstChoices.clearSelection();
Expand Down Expand Up @@ -236,6 +279,16 @@ private String getDisplayText(final T value) {
return value != null ? value.toString() : "";
}

private int indexOfInModel(final T t) {
final ListModel<T> m = lstChoices.getModel();
for (int i = 0; i < m.getSize(); i++) {
if (Objects.equals(m.getElementAt(i), t)) {
return i;
}
}
return -1;
}

/**
* Returns the FList used in the list chooser. this is useful for
* registering listeners before showing the dialog.
Expand Down Expand Up @@ -263,11 +316,14 @@ public boolean show(final Collection<T> item) {
//invoke later so selected item not set until dialog open
SwingUtilities.invokeLater(() -> {
if (item != null) {
int[] indices = item.stream()
.mapToInt(displayedItems::indexOf)
final int[] indices = item.stream()
.mapToInt(this::indexOfInModel)
.filter(i -> i >= 0)
.toArray();
lstChoices.setSelectedIndices(indices);
if (indices.length > 0) {
lstChoices.ensureIndexIsVisible(indices[0]);
}
}
else {
lstChoices.setSelectedIndex(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public final class ChangePrintingDialog {
private static final int SCROLLBAR_W = 16;
// Empirical buffer: FScrollPane's themed border eats ~4px; without this, 4*cellW+SCROLLBAR_W rounds down to 3 cols.
private static final int VIEWPORT_BUFFER = 12;
private static final int CONTAINER_H = 736; // 700 grid + 28 search bar + 8 gap
private static final int VISIBLE_ROWS = 2;
private static final int TOPBAR_H = 28;
private static final int TOPBAR_GAP = 8;
private static final int SEARCH_DEBOUNCE_MS = 200;

private ChangePrintingDialog() {}
Expand All @@ -52,7 +54,7 @@ public static PaperCard show(final PaperCard current) {
printings.add(0, currentNonFoil);
}

final CardImageGrid grid = new CardImageGrid(COLUMNS, THUMB_W, THUMB_H);
final CardImageGrid<PaperCard> grid = CardImageGrid.forPaperCards(COLUMNS, THUMB_W, THUMB_H);
grid.setItems(printings);
grid.setSelected(currentNonFoil);

Expand Down Expand Up @@ -97,7 +99,8 @@ public static PaperCard show(final PaperCard current) {
topBar.add(cbStyle, BorderLayout.EAST);

final int containerW = COLUMNS * grid.getCellWidth() + SCROLLBAR_W + VIEWPORT_BUFFER;
final Dimension fixedSize = new Dimension(containerW, CONTAINER_H);
final int containerH = VISIBLE_ROWS * grid.getCellHeight() + TOPBAR_H + TOPBAR_GAP;
final Dimension fixedSize = new Dimension(containerW, containerH);
final JPanel container = new JPanel(new BorderLayout(0, 8)) {
private static final long serialVersionUID = 1L;
@Override public Dimension getPreferredSize() { return fixedSize; }
Expand Down
Loading
Loading