diff --git a/.gitattributes b/.gitattributes
index e32e4d5cf44..d40ec7bfe1e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -14794,6 +14794,21 @@ src/main/java/forge/gui/MultiLineLabelUI.java svneol=native#text/plain
src/main/java/forge/gui/SOverlayUtils.java -text
src/main/java/forge/gui/UnsortedListModel.java -text
src/main/java/forge/gui/WrapLayout.java -text
+src/main/java/forge/gui/cardlistview/AlwaysShowToolTip.java -text
+src/main/java/forge/gui/cardlistview/EditorTableModel.java -text
+src/main/java/forge/gui/cardlistview/EditorTableView.java -text
+src/main/java/forge/gui/cardlistview/ITableContainer.java -text
+src/main/java/forge/gui/cardlistview/IntegerRenderer.java -text
+src/main/java/forge/gui/cardlistview/ManaCostRenderer.java -text
+src/main/java/forge/gui/cardlistview/SCardListViewIO.java -text
+src/main/java/forge/gui/cardlistview/SCardListViewUtil.java -text
+src/main/java/forge/gui/cardlistview/SColumnUtil.java -text
+src/main/java/forge/gui/cardlistview/SFilterUtil.java -text
+src/main/java/forge/gui/cardlistview/SetCodeRenderer.java -text
+src/main/java/forge/gui/cardlistview/TableColumnInfo.java -text
+src/main/java/forge/gui/cardlistview/TableSorter.java -text
+src/main/java/forge/gui/cardlistview/TableSorterCascade.java -text
+src/main/java/forge/gui/cardlistview/package-info.java -text
src/main/java/forge/gui/deckeditor/CDeckEditorUI.java -text
src/main/java/forge/gui/deckeditor/DeckImport.java -text
src/main/java/forge/gui/deckeditor/SEditorIO.java -text
diff --git a/src/main/java/forge/gui/cardlistview/AlwaysShowToolTip.java b/src/main/java/forge/gui/cardlistview/AlwaysShowToolTip.java
new file mode 100644
index 00000000000..2d941460cd3
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/AlwaysShowToolTip.java
@@ -0,0 +1,25 @@
+/*
+ * 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 .
+ */
+package forge.gui.cardlistview;
+
+/**
+ * A marker interface for indicating that tooltips should always be shown for
+ * cells rendered with the marked renderer.
+ */
+interface AlwaysShowToolTip {
+}
diff --git a/src/main/java/forge/gui/cardlistview/EditorTableModel.java b/src/main/java/forge/gui/cardlistview/EditorTableModel.java
new file mode 100644
index 00000000000..b0e6d5b77ac
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/EditorTableModel.java
@@ -0,0 +1,401 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011
+import forge.gui.deckeditor.views.VDeckEditorUI;
+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 .
+ */
+package forge.gui.cardlistview;
+
+import java.awt.Cursor;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map.Entry;
+
+import javax.swing.JTable;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import forge.gui.cardlistview.SColumnUtil.ColumnName;
+import forge.gui.cardlistview.SColumnUtil.SortState;
+import forge.gui.deckeditor.CDeckEditorUI;
+import forge.item.InventoryItem;
+import forge.item.ItemPool;
+import forge.item.ItemPoolView;
+
+/**
+ *
+ * EditorTableModel class.
+ *
+ *
+ * @param
+ * the generic type
+ * @author Forge
+ * @version $Id: EditorTableModel.java 19857 2013-02-24 08:49:52Z Max mtg $
+ */
+@SuppressWarnings("serial")
+public final class EditorTableModel extends AbstractTableModel {
+ private final ItemPool data;
+ private final JTable table;
+ private final CascadeManager cascadeManager = new CascadeManager();
+ private final int maxSortDepth = 3;
+ private boolean infiniteSupply;
+
+ /**
+ * Instantiates a new table model, using a JTable,
+ * a column set, and a data set of generic type .
+ *
+ * @param table0 {@link javax.swing.JTable}
+ * @param class0 Generic type
+ */
+ public EditorTableModel(final JTable table0, final Class class0) {
+ this.table = table0;
+ this.data = new ItemPool(class0);
+ }
+
+ /** */
+ @SuppressWarnings("unchecked")
+ public void setup() {
+ final Enumeration e = table.getColumnModel().getColumns();
+ final TableColumn[] sortcols = new TableColumn[table.getColumnCount()];
+
+ // Assemble priority sort.
+ while (e.hasMoreElements()) {
+ final TableColumnInfo col = (TableColumnInfo) e.nextElement();
+ if (col.getSortPriority() > 0) {
+ sortcols[col.getSortPriority()] = col;
+ }
+ }
+
+ final boolean isDeckTable = ((TableColumnInfo) table.getColumnModel()
+ .getColumn(0)).getEnumValue().substring(0, 4).equals("DECK")
+ ? true : false;
+
+ if (sortcols[1] == null) {
+ if (isDeckTable) {
+ cascadeManager.add((TableColumnInfo) SColumnUtil.getColumn(ColumnName.DECK_NAME));
+ }
+ else {
+ cascadeManager.add((TableColumnInfo) SColumnUtil.getColumn(ColumnName.CAT_NAME));
+ }
+ }
+ else {
+ ArrayUtils.reverse(sortcols);
+ for (int i = 1; i < sortcols.length; i++) {
+ if (sortcols[i] != null) {
+ cascadeManager.add((TableColumnInfo) sortcols[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Clears all data in the model.
+ */
+ public void clear() {
+ this.data.clear();
+ }
+
+ /**
+ * Gets all cards in the model.
+ *
+ * @return the cards
+ */
+ public ItemPoolView getCards() {
+ return this.data.getView();
+ }
+
+ /**
+ * Removes a card from the model.
+ *
+ * @param card0 {@link forge.Card} object
+ */
+ public void removeCard(final T card0, int qty) {
+ if ( isInfinite() )
+ return;
+
+ final boolean wasThere = this.data.count(card0) > 0;
+ if (wasThere) {
+ this.data.remove(card0, qty);
+ this.fireTableDataChanged();
+ }
+ }
+
+ /**
+ * Adds a card to the model.
+ *
+ * @param card0 {@link forge.Card} object.
+ */
+ public void addCard(final T card0, int qty) {
+ this.data.add(card0, qty);
+ this.fireTableDataChanged();
+ }
+
+ /**
+ * Adds multiple copies of multiple cards to the model.
+ *
+ * @param cards0 {@link java.lang.Iterable}>
+ */
+ public void addCards(final Iterable> cards0) {
+ this.data.addAll(cards0);
+ this.fireTableDataChanged();
+ }
+
+ /**
+ * Row to card.
+ *
+ * @param row
+ * the row
+ * @return the entry
+ */
+ public Entry rowToCard(final int row) {
+ final List> model = this.data.getOrderedList();
+ return (row >= 0) && (row < model.size()) ? model.get(row) : null;
+ }
+
+ /**
+ * Show selected card.
+ *
+ * @param table
+ * the table
+ */
+ public void showSelectedCard(final JTable table) {
+ final int row = table.getSelectedRow();
+ if (row != -1) {
+ Entry card = this.rowToCard(row);
+ CDeckEditorUI.SINGLETON_INSTANCE.setCard(null != card ? card.getKey() : null);
+ }
+ }
+
+ /**
+ *
+ * addListeners.
+ *
+ */
+ public void addListeners() {
+ // updates card detail, listens to any key strokes
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(final ListSelectionEvent arg0) {
+ if (table.isFocusOwner()) {
+ EditorTableModel.this.showSelectedCard(table);
+ }
+ }
+ });
+
+ table.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusGained(final FocusEvent e) {
+ EditorTableModel.this.showSelectedCard(table);
+ }
+ });
+
+ final JTableHeader header = table.getTableHeader();
+ header.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ if (Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) != header.getCursor()) {
+ headerClicked(e);
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ SCardListViewIO.savePreferences();
+ }
+ });
+ } // addCardListener()
+
+ /**
+ * Resort.
+ */
+ public void refreshSort() {
+ if (this.data.getOrderedList().size() == 0) { return; }
+
+ Collections.sort(this.data.getOrderedList(), new MyComparator());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void headerClicked(final MouseEvent e) {
+ final TableColumnModel colModel = EditorTableModel.this.table.getColumnModel();
+ final int columnModelIndex = colModel.getColumnIndexAtX(e.getX());
+ final int modelIndex = colModel.getColumn(columnModelIndex).getModelIndex();
+
+ if (modelIndex < 0) {
+ return;
+ }
+
+ // This will invert if needed
+ // 2012/07/21 - Changed from modelIndex to ColumnModelIndex due to a crash
+ // Crash was: Hide 2 columns, then search by last column.
+ EditorTableModel.this.cascadeManager.add((TableColumnInfo) this.table.getColumnModel().getColumn(columnModelIndex));
+ EditorTableModel.this.refreshSort();
+ EditorTableModel.this.table.tableChanged(new TableModelEvent(EditorTableModel.this));
+ EditorTableModel.this.table.repaint();
+ if (EditorTableModel.this.table.getRowCount() > 0) {
+ EditorTableModel.this.table.setRowSelectionInterval(0, 0);
+ }
+ SCardListViewIO.savePreferences();
+ }
+
+ //========== Overridden from AbstractTableModel
+ /** {@inheritDoc} */
+ @Override
+ public int findColumn(final String name0) {
+ return table.getColumnModel().getColumnIndex(name0);
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.table.TableModel#getColumnCount()
+ */
+ @Override
+ public int getColumnCount() {
+ return table.getColumnCount();
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.table.TableModel#getRowCount()
+ */
+ @Override
+ public int getRowCount() {
+ return this.data.countDistinct();
+ }
+
+ /* (non-Javadoc)
+ * @see javax.swing.table.TableModel#getValueAt(int, int)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object getValueAt(int iRow, int iCol) {
+ Entry card = this.rowToCard(iRow);
+ if (null == card) {
+ return null;
+ }
+ return ((TableColumnInfo) table.getColumnModel().getColumn(table.convertColumnIndexToView(iCol))).getFnDisplay().apply(card);
+ }
+
+ //========= Custom class handling
+
+ /**
+ * Manages sorting orders for multiple depths of sorting.
+ */
+ private final class CascadeManager {
+ private final List> colsToSort = new ArrayList>(3);
+ private TableSorterCascade sorter = null;
+
+ // Adds a column to sort cascade list.
+ // If column is first in the cascade, inverts direction of sort.
+ // Otherwise, sorts in ascending direction.
+ @SuppressWarnings("unchecked")
+ public void add(final TableColumnInfo col0) {
+ this.sorter = null;
+
+ // Found at top level, should invert
+ if (colsToSort.size() > 0 && colsToSort.get(0).equals(col0)) {
+ this.colsToSort.get(0).setSortState(
+ this.colsToSort.get(0).getSortState() == SortState.ASC
+ ? SortState.DESC : SortState.ASC);
+ this.colsToSort.get(0).setSortPriority(1);
+ }
+ // Found somewhere: move down others, this one to top.
+ else if (colsToSort.contains(col0)) {
+ col0.setSortState(SortState.ASC);
+ this.colsToSort.remove(col0);
+ this.colsToSort.add(0, (TableColumnInfo) col0);
+ }
+ // No column in list; add directly.
+ else {
+ col0.setSortState(SortState.ASC);
+ this.colsToSort.add(0, (TableColumnInfo) col0);
+ this.colsToSort.get(0).setSortPriority(1);
+ }
+
+ // Decrement sort priority on remaining columns
+ for (int i = 1; i < maxSortDepth; i++) {
+ if (colsToSort.size() == i) { break; }
+
+ if (colsToSort.get(i).getSortPriority() != 0) {
+ colsToSort.get(i).setSortPriority(i + 1);
+ }
+ }
+
+ // Unset and remove boundary columns.
+ if (this.colsToSort.size() > maxSortDepth) {
+ this.colsToSort.get(maxSortDepth).setSortState(SortState.NONE);
+ this.colsToSort.get(maxSortDepth).setSortPriority(0);
+ this.colsToSort.remove(maxSortDepth);
+ }
+ }
+
+ public TableSorterCascade getSorter() {
+ if (this.sorter == null) {
+ this.sorter = createSorter();
+ }
+ return this.sorter;
+ }
+
+ private TableSorterCascade createSorter() {
+
+ final List> oneColSorters
+ = new ArrayList>(maxSortDepth);
+
+ for (final TableColumnInfo col : this.colsToSort) {
+ oneColSorters.add(new TableSorter(
+ col.getFnSort(),
+ col.getSortState().equals(SortState.ASC) ? true : false));
+ }
+
+ return new TableSorterCascade(oneColSorters);
+ }
+ }
+
+ private class MyComparator implements Comparator> {
+ /* (non-Javadoc)
+ * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public int compare(Entry o1, Entry o2) {
+ return EditorTableModel.this.cascadeManager.getSorter().compare(
+ (Entry) o1, (Entry) o2);
+ }
+ }
+
+ /**
+ * Sets whether this table's pool of cards is in infinite supply. If false, cards in the
+ * table have a limited number of copies.
+ */
+ public void setInfinite(boolean infinite) {
+ this.infiniteSupply = infinite;
+ }
+
+ public boolean isInfinite() {
+ return infiniteSupply;
+ }
+} // CardTableModel
diff --git a/src/main/java/forge/gui/cardlistview/EditorTableView.java b/src/main/java/forge/gui/cardlistview/EditorTableView.java
new file mode 100644
index 00000000000..c19584a5306
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/EditorTableView.java
@@ -0,0 +1,576 @@
+/*
+ * 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 .
+ */
+package forge.gui.cardlistview;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.KeyboardFocusManager;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.swing.JComponent;
+import javax.swing.JTable;
+import javax.swing.JViewport;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+import forge.gui.deckeditor.SEditorUtil;
+import forge.gui.deckeditor.views.ITableContainer;
+import forge.gui.toolbox.FSkin;
+import forge.item.InventoryItem;
+import forge.item.ItemPool;
+import forge.item.ItemPoolView;
+import forge.util.Aggregates;
+
+
+/**
+ * TableWithCards.
+ *
+ * @param
+ * the generic type
+ */
+public final class EditorTableView {
+ private ItemPool pool;
+ private EditorTableModel model;
+ private final JTable table;
+ private Predicate filter = null;
+ private boolean wantUnique = false;
+ private boolean alwaysNonUnique = false;
+
+ private final Class genericType;
+
+ /**
+ *
+ * getTable.
+ *
+ * @return JTable
+ */
+ public JTable getTable() {
+ return this.table;
+ }
+
+ /**
+ * TableWithCards.
+ *
+ * @param type0 the class of item that this table will contain
+ */
+ public EditorTableView(final Class type0) {
+ this(false, type0);
+ }
+
+ /**
+ * TableWithCards Constructor.
+ *
+ * @param forceUnique whether this table should display only one item with the same name
+ * @param type0 the class of item that this table will contain
+ */
+ @SuppressWarnings("serial")
+ public EditorTableView(final boolean forceUnique, final Class type0) {
+ this.genericType = type0;
+ this.wantUnique = forceUnique;
+
+ // subclass JTable to show tooltips when hovering over column headers
+ // and cell data that are truncated due to too-small column widths
+ table = new JTable() {
+ private String _getCellTooltip(
+ TableCellRenderer renderer, int row, int col, Object val) {
+ Component cell = renderer.getTableCellRendererComponent(
+ table, val, false, false, row, col);
+
+ // if we're conditionally showing the tooltip, check to see
+ // if we shouldn't show it
+ if (!(cell instanceof AlwaysShowToolTip))
+ {
+ // if there's enough room (or there's no value), no tooltip
+ // we use '>' here instead of '>=' since that seems to be the
+ // threshold for where the ellipses appear for the default
+ // JTable renderer
+ int requiredWidth = cell.getPreferredSize().width;
+ TableColumn tableColumn = columnModel.getColumn(col);
+ if (null == val || tableColumn.getWidth() > requiredWidth) {
+ return null;
+ }
+ }
+
+ // use a pre-set tooltip if it exists
+ if (cell instanceof JComponent)
+ {
+ JComponent jcell = (JComponent)cell;
+ String tip = jcell.getToolTipText();
+ if (null != tip)
+ {
+ return tip;
+ }
+ }
+
+ // otherwise, show the full text in the tooltip
+ return String.valueOf(val);
+ }
+
+ // column headers
+ @Override
+ protected JTableHeader createDefaultTableHeader() {
+ return new JTableHeader(columnModel) {
+ public String getToolTipText(MouseEvent e) {
+ int col = columnModel.getColumnIndexAtX(e.getPoint().x);
+ TableColumn tableColumn = columnModel.getColumn(col);
+ TableCellRenderer headerRenderer = tableColumn.getHeaderRenderer();
+ if (null == headerRenderer) {
+ headerRenderer = getDefaultRenderer();
+ }
+
+ return _getCellTooltip(
+ headerRenderer, -1, col, tableColumn.getHeaderValue());
+ }
+ };
+ }
+
+ // cell data
+ @Override
+ public String getToolTipText(MouseEvent e) {
+ Point p = e.getPoint();
+ int row = rowAtPoint(p);
+ int col = columnAtPoint(p);
+
+ if (col >= table.getColumnCount() || row >= table.getRowCount()) {
+ return null;
+ }
+
+ Object val = table.getValueAt(row, col);
+ if (null == val) {
+ return null;
+ }
+
+ return _getCellTooltip(getCellRenderer(row, col), row, col, val);
+ }
+
+ private int lastTooltipRow = -1;
+ private int lastTooltipCol = -1;
+ private Point lastTooltipPt;
+
+ @Override
+ public Point getToolTipLocation(MouseEvent e) {
+ Point p = e.getPoint();
+ final int row = rowAtPoint(p);
+ final int col = columnAtPoint(p);
+ if (row == lastTooltipRow && col == lastTooltipCol) {
+ p = lastTooltipPt;
+ } else {
+ lastTooltipRow = row;
+ lastTooltipCol = col;
+ lastTooltipPt = p;
+ }
+ return new Point(p.x + 10, p.y + 20);
+ }
+ };
+
+ // use different selection highlight colors for focused vs. unfocused tables
+ table.setSelectionBackground(FSkin.getColor(FSkin.Colors.CLR_INACTIVE));
+ table.setSelectionForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT));
+ table.addFocusListener(new FocusListener() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ if (!e.isTemporary()) {
+ table.setSelectionBackground(FSkin.getColor(FSkin.Colors.CLR_INACTIVE));
+ }
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ table.setSelectionBackground(FSkin.getColor(FSkin.Colors.CLR_ACTIVE));
+ // if nothing selected when we gain focus, select the first row (if exists)
+ if (-1 == table.getSelectedRow() && 0 < table.getRowCount()) {
+ table.setRowSelectionInterval(0, 0);
+ }
+ }
+ });
+
+ table.setFont(FSkin.getFont(12));
+ table.setBorder(null);
+ table.getTableHeader().setBorder(null);
+ table.setRowHeight(18);
+ table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
+
+ // prevent tables from intercepting tab focus traversals
+ table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
+ table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
+ }
+
+ /**
+ * Applies a EditorTableModel and a model listener to this instance's JTable.
+ *
+ * @param view0 the {@link javax.gui.deckeditor.views.ITableCOntainer}
+ * @param cols0 List> of additional columns for this
+ */
+ public void setup(final ITableContainer view0, final List> cols0) {
+ final DefaultTableColumnModel colmodel = new DefaultTableColumnModel();
+
+ for (TableColumnInfo item : cols0) {
+ item.setModelIndex(colmodel.getColumnCount());
+ if (item.isShowing()) { colmodel.addColumn(item); }
+ }
+
+ this.model = new EditorTableModel(this.table, this.genericType);
+ this.model.addListeners();
+ this.table.setModel(this.model);
+ this.table.setColumnModel(colmodel);
+
+ this.model.setup();
+ this.model.refreshSort();
+
+ this.table.getTableHeader().setBackground(new Color(200, 200, 200));
+
+ // Update stats each time table changes
+ this.model.addTableModelListener(new TableModelListener() {
+ @Override
+ public void tableChanged(final TableModelEvent ev) {
+ SEditorUtil.setStats(EditorTableView.this.model.getCards(), view0);
+ }
+ });
+ }
+
+ public void setAvailableColumns(final List> cols0) {
+ final DefaultTableColumnModel colmodel = new DefaultTableColumnModel();
+
+ for (TableColumnInfo item : cols0) {
+ item.setModelIndex(colmodel.getColumnCount());
+ if (item.isShowing()) { colmodel.addColumn(item); }
+ }
+
+ this.table.setColumnModel(colmodel);
+ }
+
+ /**
+ *
+ * fixSelection. Call this after deleting an item from table.
+ *
+ * @param rowLastSelected
+ * an int
+ */
+ public void fixSelection(final int rowLastSelected) {
+ if (0 > rowLastSelected) {
+ return;
+ }
+
+ // 3 cases: 0 cards left, select the same row, select prev row
+ int numRows = model.getRowCount();
+ if (numRows == 0) {
+ return;
+ }
+
+ int newRow = rowLastSelected;
+ if (numRows <= newRow) {
+ // move selection away from the last, already missing, option
+ newRow = numRows - 1;
+ }
+
+ selectAndScrollTo(newRow);
+ }
+
+ /**
+ *
+ * setDeck.
+ *
+ * @param cards
+ * an Iterable
+ */
+ public void setDeck(final Iterable cards) {
+ this.setDeckImpl(ItemPool.createFrom(cards, this.genericType), false);
+ }
+
+ /**
+ * setDeck.
+ *
+ * @param poolView
+ * an ItemPoolView
+ */
+ public void setDeck(final ItemPoolView poolView, boolean infinite) {
+ this.setDeckImpl(ItemPool.createFrom(poolView, this.genericType), infinite);
+
+ }
+ public void setDeck(final ItemPoolView poolView) {
+ this.setDeck(poolView, false);
+ }
+ /**
+ * Sets the deck.
+ *
+ * @param pool
+ * the new deck
+ */
+ public void setDeck(final ItemPool pool) {
+ this.setDeckImpl(pool, false);
+ }
+
+ /**
+ *
+ * setDeckImpl.
+ *
+ * @param thePool
+ * an ItemPool
+ */
+ protected void setDeckImpl(final ItemPool thePool, boolean infinite) {
+ this.model.clear();
+ this.pool = thePool;
+ this.model.addCards(this.pool);
+ this.model.setInfinite(infinite);
+ this.updateView(true);
+ }
+
+ /**
+ *
+ * getSelectedCard.
+ *
+ * @return InventoryItem
+ */
+ public InventoryItem getSelectedCard() {
+ final int iRow = this.table.getSelectedRow();
+ return iRow >= 0 ? this.model.rowToCard(iRow).getKey() : null;
+ }
+
+ /**
+ * returns all selected cards
+ */
+ public List getSelectedCards() {
+ List items = new ArrayList();
+ for (int row : table.getSelectedRows()) {
+ items.add(model.rowToCard(row).getKey());
+ }
+ return items;
+ }
+
+ private boolean isUnfiltered() {
+ return this.filter == null;
+ }
+
+ /**
+ *
+ * setFilter.
+ *
+ * @param filterToSet
+ * a Predicate
+ */
+ public void setFilter(final Predicate filterToSet) {
+ this.filter = filterToSet;
+ if (null != pool) {
+ this.updateView(true);
+ }
+ }
+
+ /**
+ *
+ * addCard.
+ *
+ * @param card
+ * an InventoryItem
+ */
+ public void addCard(final T card, int qty) {
+ final int n = this.table.getSelectedRow();
+ this.pool.add(card, qty);
+ if (this.isUnfiltered()) {
+ this.model.addCard(card, qty);
+ }
+ this.updateView(false);
+ this.fixSelection(n);
+ }
+
+ public void addCards(Iterable> cardsToAdd) {
+ final int n = this.table.getSelectedRow();
+ for (Map.Entry item : cardsToAdd) {
+ this.pool.add(item.getKey(), item.getValue());
+ if (this.isUnfiltered()) {
+ this.model.addCard(item.getKey(), item.getValue());
+ }
+ }
+ this.updateView(false);
+ this.fixSelection(n);
+ }
+
+ public void addCards(Collection cardsToAdd) {
+ final int n = this.table.getSelectedRow();
+ for (T item : cardsToAdd) {
+ this.pool.add(item, 1);
+ if (this.isUnfiltered()) {
+ this.model.addCard(item, 1);
+ }
+ }
+ this.updateView(false);
+ this.fixSelection(n);
+ }
+
+ /**
+ *
+ * removeCard.
+ *
+ * @param card
+ * an InventoryItem
+ */
+ public void removeCard(final T card, int qty) {
+ final int n = this.table.getSelectedRow();
+ this.pool.remove(card, qty);
+ if (this.isUnfiltered()) {
+ this.model.removeCard(card, qty);
+ }
+ this.updateView(false);
+ this.fixSelection(n);
+ }
+
+ public void removeCards(List> cardsToRemove) {
+ final int n = this.table.getSelectedRow();
+ for (Map.Entry item : cardsToRemove) {
+ this.pool.remove(item.getKey(), item.getValue());
+ if (this.isUnfiltered()) {
+ this.model.removeCard(item.getKey(), item.getValue());
+ }
+ }
+ this.updateView(false);
+ this.fixSelection(n);
+ }
+
+ public int getCardCount(final T card) {
+ return model.isInfinite() ? Integer.MAX_VALUE : this.pool.count(card);
+ }
+
+ public Predicate getFilter() {
+ return filter;
+ }
+
+ /**
+ *
+ * updateView.
+ *
+ * @param bForceFilter
+ * a boolean
+ */
+ public void updateView(final boolean bForceFilter) {
+ final boolean useFilter = (bForceFilter && (this.filter != null)) || !isUnfiltered();
+
+ if (useFilter || this.wantUnique || bForceFilter) {
+ this.model.clear();
+ }
+
+ if (useFilter && this.wantUnique) {
+ Predicate> filterForPool = Predicates.compose(this.filter, this.pool.FN_GET_KEY);
+ Iterable> cards = Aggregates.uniqueByLast(Iterables.filter(this.pool, filterForPool), this.pool.FN_GET_NAME);
+ this.model.addCards(cards);
+ } else if (useFilter) {
+ Predicate> pred = Predicates.compose(this.filter, this.pool.FN_GET_KEY);
+ this.model.addCards(Iterables.filter(this.pool, pred));
+ } else if (this.wantUnique) {
+ Iterable> cards = Aggregates.uniqueByLast(this.pool, this.pool.FN_GET_NAME);
+ this.model.addCards(cards);
+ } else if (!useFilter && bForceFilter) {
+ this.model.addCards(this.pool);
+ }
+
+ this.model.refreshSort();
+ }
+
+ /**
+ *
+ * getCards.
+ *
+ * @return ItemPoolView
+ */
+ public ItemPoolView getCards() {
+ return this.pool;
+ }
+
+ /**
+ *
+ * getWantUnique.
+ *
+ * @return true if the editor is in "unique card names only" mode.
+ */
+ public boolean getWantUnique() {
+ return this.wantUnique;
+ }
+
+ /**
+ *
+ * setWantUnique.
+ *
+ * @param unique if true, the editor will be set to the "unique card names only" mode.
+ */
+ public void setWantUnique(boolean unique) {
+ this.wantUnique = this.alwaysNonUnique ? false : unique;
+ }
+
+ /**
+ *
+ * getAlwaysNonUnique.
+ *
+ * @return if ture, this editor must always show non-unique cards (e.g. quest editor).
+ */
+ public boolean getAlwaysNonUnique() {
+ return this.alwaysNonUnique;
+ }
+
+ /**
+ *
+ * setAlwaysNonUnique.
+ *
+ * @param nonUniqueOnly if true, this editor must always show non-unique cards (e.g. quest editor).
+ */
+ public void setAlwaysNonUnique(boolean nonUniqueOnly) {
+ this.alwaysNonUnique = nonUniqueOnly;
+ }
+
+ public void setWantElasticColumns(boolean value) {
+ table.setAutoResizeMode(value ? JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS : JTable.AUTO_RESIZE_NEXT_COLUMN);
+ }
+
+ public void selectAndScrollTo(int rowIdx) {
+ if (!(table.getParent() instanceof JViewport)) {
+ return;
+ }
+ JViewport viewport = (JViewport)table.getParent();
+
+ // compute where we're going and where we are
+ Rectangle targetRect = table.getCellRect(rowIdx, 0, true);
+ Rectangle curViewRect = viewport.getViewRect();
+
+ // if the target cell is not visible, attempt to jump to a location where it is
+ // visible but not on the edge of the viewport
+ if (targetRect.y + targetRect.height > curViewRect.y + curViewRect.height) {
+ // target is below us, move to position 3 rows below target
+ targetRect.setLocation(targetRect.x, targetRect.y + (targetRect.height * 3));
+ } else if (targetRect.y < curViewRect.y) {
+ // target is above is, move to position 3 rows above target
+ targetRect.setLocation(targetRect.x, targetRect.y - (targetRect.height * 3));
+ }
+
+ table.scrollRectToVisible(targetRect);
+ table.setRowSelectionInterval(rowIdx, rowIdx);
+ }
+}
diff --git a/src/main/java/forge/gui/cardlistview/ITableContainer.java b/src/main/java/forge/gui/cardlistview/ITableContainer.java
new file mode 100644
index 00000000000..9cbc42c6327
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/ITableContainer.java
@@ -0,0 +1,25 @@
+package forge.gui.cardlistview;
+
+import javax.swing.JTable;
+
+import forge.gui.deckeditor.SEditorUtil;
+import forge.gui.toolbox.FLabel;
+
+/**
+ * Dictates methods needed for a class to act as a container for
+ * a EditorTableView deck editing component.
+ *
+ *
(I at beginning of class name denotes an interface.)
+ *
+ */
+public interface ITableContainer {
+ /**
+ * Sets the table used for displaying cards in this
+ * deck editor container.
+ *
+ * @param tbl0 {@link forge.gui.deckeditor.tables.EditorTableView}
+ */
+ void setTableView(JTable tbl0);
+
+ FLabel getStatLabel(SCardListViewUtil.StatTypes s);
+ }
diff --git a/src/main/java/forge/gui/cardlistview/IntegerRenderer.java b/src/main/java/forge/gui/cardlistview/IntegerRenderer.java
new file mode 100644
index 00000000000..df2e72e0ff3
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/IntegerRenderer.java
@@ -0,0 +1,44 @@
+/*
+ * 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 .
+ */
+package forge.gui.cardlistview;
+
+import java.awt.Component;
+
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * A quick converter to avoid -1 being displayed for unapplicable values.
+ */
+@SuppressWarnings("serial")
+public class IntegerRenderer extends DefaultTableCellRenderer {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent
+ * (javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ @Override
+ public final Component getTableCellRendererComponent(final JTable table, Object value0,
+ final boolean isSelected, final boolean hasFocus, final int row, final int column) {
+
+ if ((Integer) value0 == -1) { value0 = "-"; }
+ return super.getTableCellRendererComponent(table, value0, isSelected, hasFocus, row, column);
+ }
+}
diff --git a/src/main/java/forge/gui/cardlistview/ManaCostRenderer.java b/src/main/java/forge/gui/cardlistview/ManaCostRenderer.java
new file mode 100644
index 00000000000..80c7f81bf61
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/ManaCostRenderer.java
@@ -0,0 +1,135 @@
+/*
+ * 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 .
+ */
+package forge.gui.cardlistview;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.util.List;
+
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import forge.card.CardRules;
+import forge.card.CardSplitType;
+import forge.card.mana.ManaCost;
+import forge.card.mana.ManaCostShard;
+import forge.gui.toolbox.CardFaceSymbols;
+
+/**
+ * Displays mana cost as symbols.
+ */
+public class ManaCostRenderer extends DefaultTableCellRenderer {
+ private static final long serialVersionUID = 1770527102334163549L;
+
+ static final int elemtWidth = 13;
+ static final int elemtGap = 0;
+ static final int padding0 = 1;
+ static final int spaceBetweenSplitCosts = 3;
+
+ private ManaCost v1;
+ private ManaCost v2;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent
+ * (javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ @Override
+ public final Component getTableCellRendererComponent(final JTable table, final Object value,
+ final boolean isSelected, final boolean hasFocus, final int row, final int column) {
+ CardRules v = value instanceof CardRules ? (CardRules) value : null;
+ this.v1 = v == null ? ManaCost.NO_COST : v.getMainPart().getManaCost();
+ this.v2 = v == null || v.getSplitType() != CardSplitType.Split ? null : v.getOtherPart().getManaCost();
+ this.setToolTipText(v2 == null ? v1.toString() : v1.toString() + " / " + v2.toString());
+ return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public final void paint(final Graphics g) {
+ super.paint(g);
+
+ final int cellWidth = this.getWidth();
+
+ if ( null == v2 )
+ drawCost(g, v1, padding0, cellWidth);
+ else
+ {
+ int shards1 = v1.isPureGeneric() || v1.getGenericCost() > 0 ? 1 : 0;
+ int shards2 = v2.isPureGeneric() || v2.getGenericCost() > 0 ? 1 : 0;
+ shards1 += v1.getShards().size();
+ shards2 += v2.getShards().size();
+
+ int perGlyph = (cellWidth - padding0 - spaceBetweenSplitCosts) / (shards1 + shards2);
+ perGlyph = Math.min(perGlyph, elemtWidth + elemtGap);
+ drawCost(g, v1, padding0, padding0 + perGlyph * shards1);
+ drawCost(g, v2, cellWidth - perGlyph * shards2, cellWidth );
+ }
+ }
+
+ /**
+ * TODO: Write javadoc for this method.
+ * @param g
+ * @param padding
+ * @param cellWidth
+ */
+ private void drawCost(final Graphics g, ManaCost value, final int padding, final int cellWidth) {
+ float xpos = padding;
+ final int genericManaCost = value.getGenericCost();
+ final int xManaCosts = value.countX();
+ final boolean hasGeneric = (genericManaCost > 0) || this.v1.isPureGeneric();
+ final List shards = value.getShards();
+
+
+ final int cntGlyphs = hasGeneric ? shards.size() + 1 : shards.size();
+ final float offsetIfNoSpace = cntGlyphs > 1 ? (cellWidth - padding - elemtWidth) / (cntGlyphs - 1f)
+ : elemtWidth + elemtGap;
+ final float offset = Math.min(elemtWidth + elemtGap, offsetIfNoSpace);
+
+ // Display X Mana before any other type of mana
+ if (xManaCosts > 0) {
+ for (int i = 0; i < xManaCosts; i++) {
+ CardFaceSymbols.drawSymbol(ManaCostShard.X.getImageKey(), g, (int) xpos, 1);
+ xpos += offset;
+ }
+ }
+
+ // Display colorless mana before colored mana
+ if (hasGeneric) {
+ final String sGeneric = Integer.toString(genericManaCost);
+ CardFaceSymbols.drawSymbol(sGeneric, g, (int) xpos, 1);
+ xpos += offset;
+ }
+
+ for (final ManaCostShard s : shards) {
+ if (s.equals(ManaCostShard.X)) {
+ // X costs already drawn up above
+ continue;
+ }
+ CardFaceSymbols.drawSymbol(s.getImageKey(), g, (int) xpos, 1);
+ xpos += offset;
+ }
+ }
+
+}
diff --git a/src/main/java/forge/gui/cardlistview/SCardListViewIO.java b/src/main/java/forge/gui/cardlistview/SCardListViewIO.java
new file mode 100644
index 00000000000..2c873288876
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/SCardListViewIO.java
@@ -0,0 +1,239 @@
+package forge.gui.cardlistview;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import forge.gui.cardlistview.SColumnUtil.ColumnName;
+import forge.item.InventoryItem;
+import forge.properties.NewConstants;
+
+/**
+ * Handles editor preferences saving and loading.
+ *
+ *
(S at beginning of class name denotes a static factory.)
+ */
+public class SCardListViewIO {
+ /** Used in the XML IO to extract properties from PREFS file. */
+ private enum ColumnProperty { /** */
+ enumval, /** */
+ identifier, /** */
+ show, /** */
+ sortpriority, /** */
+ sortstate, /** */
+ width
+ }
+
+ /** Preferences (must match with PREFS file). */
+ public enum EditorPreference {
+ stats_deck,
+ display_unique_only,
+ elastic_columns
+ }
+
+ private static final XMLEventFactory EVENT_FACTORY = XMLEventFactory.newInstance();
+ private static final XMLEvent NEWLINE = EVENT_FACTORY.createDTD("\n");
+ private static final XMLEvent TAB = EVENT_FACTORY.createDTD("\t");
+
+ private static final Map PREFS
+ = new HashMap();
+
+ private static final Map> COLS
+ = new TreeMap>();
+
+ /**
+ * Retrieve a preference from the editor preference map.
+ *
+ * @param name0 {@link forge.gui.deckeditor.SCardListViewUtil.EditorPreference}
+ * @return TableColumnInfo
+ */
+ public static boolean getPref(final EditorPreference name0) {
+ return PREFS.get(name0);
+ }
+
+ /**
+ * Set a preference in the editor preference map.
+ *
+ * @param name0 {@link forge.gui.deckeditor.SCardListViewUtil.EditorPreference}
+ * @param val0 boolean
+ */
+ public static void setPref(final EditorPreference name0, final boolean val0) {
+ PREFS.put(name0, val0);
+ }
+
+ /**
+ * Retrieve a custom column.
+ *
+ * @param name0 {@link forge.gui.deckeditor.SCardListViewUtil.CatalogColumnName}
+ * @return TableColumnInfo
+ */
+ public static TableColumnInfo getColumn(final ColumnName name0) {
+ return COLS.get(name0);
+ }
+
+ /** Publicly-accessible save method, to neatly handle exception handling. */
+ public static void savePreferences() {
+ try { save(); }
+ catch (final Exception e) { e.printStackTrace(); }
+ }
+
+ /** Publicly-accessible load method, to neatly handle exception handling. */
+ public static void loadPreferences() {
+ try { load(); }
+ catch (final Exception e) { e.printStackTrace(); }
+ }
+
+ /**
+ *
+ * TODO: Write javadoc for this method.
+ *
+ * @param extends InventoryItem
+ * @param extends DeckBase
+ */
+ private static void save() throws Exception {
+ final XMLOutputFactory out = XMLOutputFactory.newInstance();
+ final XMLEventWriter writer = out.createXMLEventWriter(new FileOutputStream(NewConstants.EDITOR_PREFERENCES_FILE.userPrefLoc));
+
+ writer.add(EVENT_FACTORY.createStartDocument());
+ writer.add(NEWLINE);
+ writer.add(EVENT_FACTORY.createStartElement("", "", "preferences"));
+ writer.add(EVENT_FACTORY.createAttribute("type", "editor"));
+ writer.add(NEWLINE);
+
+ for (final EditorPreference p : PREFS.keySet()) {
+ writer.add(TAB);
+ writer.add(EVENT_FACTORY.createStartElement("", "", "pref"));
+ writer.add(EVENT_FACTORY.createAttribute(
+ "name", p.toString()));
+ writer.add(EVENT_FACTORY.createAttribute(
+ "value", PREFS.get(p).toString()));
+ writer.add(EVENT_FACTORY.createEndElement("", "", "pref"));
+ writer.add(NEWLINE);
+ }
+
+ for (final ColumnName c : COLS.keySet()) {
+ // If column is not in view, retain previous model index for the next time
+ // that the column will be in the view.
+ int index = SColumnUtil.getColumnViewIndex(c);
+ if (index == -1) {
+ index = COLS.get(c).getModelIndex();
+ }
+
+ writer.add(TAB);
+ writer.add(EVENT_FACTORY.createStartElement("", "", "col"));
+ writer.add(EVENT_FACTORY.createAttribute(
+ ColumnProperty.enumval.toString(), COLS.get(c).getEnumValue()));
+ writer.add(EVENT_FACTORY.createAttribute(
+ ColumnProperty.identifier.toString(), COLS.get(c).getIdentifier().toString()));
+ writer.add(EVENT_FACTORY.createAttribute(
+ ColumnProperty.show.toString(), String.valueOf(COLS.get(c).isShowing())));
+ writer.add(EVENT_FACTORY.createAttribute(
+ ColumnProperty.sortpriority.toString(), String.valueOf(COLS.get(c).getSortPriority())));
+ writer.add(EVENT_FACTORY.createAttribute(
+ ColumnProperty.sortstate.toString(), String.valueOf(COLS.get(c).getSortState())));
+ writer.add(EVENT_FACTORY.createAttribute(
+ ColumnProperty.width.toString(), String.valueOf(COLS.get(c).getWidth())));
+ writer.add(EVENT_FACTORY.createEndElement("", "", "col"));
+ writer.add(NEWLINE);
+ }
+
+ writer.add(EVENT_FACTORY.createEndDocument());
+ writer.flush();
+ writer.close();
+ }
+
+ private static void load() throws Exception {
+ final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+
+ PREFS.clear();
+ COLS.clear();
+
+ // read in defaults
+ loadPrefs(inputFactory.createXMLEventReader(new FileInputStream(NewConstants.EDITOR_PREFERENCES_FILE.defaultLoc)));
+
+ try {
+ // overwrite defaults with user preferences, if they exist
+ loadPrefs(inputFactory.createXMLEventReader(new FileInputStream(NewConstants.EDITOR_PREFERENCES_FILE.userPrefLoc)));
+ } catch (FileNotFoundException e) {
+ /* ignore; it's ok if this file doesn't exist */
+ } finally {
+ SColumnUtil.attachSortAndDisplayFunctions();
+ }
+ }
+
+ private static void loadPrefs(final XMLEventReader reader) throws XMLStreamException {
+ XMLEvent event;
+ StartElement element;
+ Iterator> attributes;
+ Attribute attribute;
+ EditorPreference pref;
+ TableColumnInfo tempcol;
+ String tagname;
+
+ while (reader.hasNext()) {
+ event = reader.nextEvent();
+
+ if (event.isStartElement()) {
+ element = event.asStartElement();
+ tagname = element.getName().getLocalPart();
+
+ // Assemble preferences
+ if (tagname.equals("pref")) {
+ // Retrieve name of pref
+ attributes = element.getAttributes();
+ try {
+ pref = EditorPreference.valueOf(((Attribute) attributes.next()).getValue());
+
+ // Add to map
+ PREFS.put(pref, Boolean.valueOf(((Attribute) attributes.next()).getValue()));
+ } catch (IllegalArgumentException e) { /* ignore; just don't use */ }
+ }
+ // Assemble columns
+ else if (tagname.equals("col")) {
+ attributes = element.getAttributes();
+ tempcol = new TableColumnInfo();
+
+ while (attributes.hasNext()) {
+ attribute = (Attribute) attributes.next();
+ if (attribute.getName().toString().equals(ColumnProperty.enumval.toString())) {
+ try { COLS.put(ColumnName.valueOf(attribute.getValue()), tempcol); }
+ catch (final Exception e) { /* ignore invalid entries */ }
+
+ tempcol.setEnumValue(attribute.getValue());
+ }
+ else if (attribute.getName().toString().equals(ColumnProperty.identifier.toString())) {
+ tempcol.setIdentifier(attribute.getValue());
+ tempcol.setHeaderValue(attribute.getValue());
+ }
+ else if (attribute.getName().toString().equals(ColumnProperty.width.toString())) {
+ tempcol.setPreferredWidth(Integer.valueOf(attribute.getValue()));
+ }
+ else if (attribute.getName().toString().equals(ColumnProperty.show.toString())) {
+ tempcol.setShowing(Boolean.valueOf(attribute.getValue()));
+ }
+ else if (attribute.getName().toString().equals(ColumnProperty.sortpriority.toString())) {
+ tempcol.setSortPriority(Integer.valueOf(attribute.getValue()));
+ }
+ else if (attribute.getName().toString().equals(ColumnProperty.sortstate.toString())) {
+ tempcol.setSortState(SortState.valueOf(attribute.getValue().toString()));
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/forge/gui/cardlistview/SCardListViewUtil.java b/src/main/java/forge/gui/cardlistview/SCardListViewUtil.java
new file mode 100644
index 00000000000..08d2a26cbbf
--- /dev/null
+++ b/src/main/java/forge/gui/cardlistview/SCardListViewUtil.java
@@ -0,0 +1,135 @@
+package forge.gui.cardlistview;
+
+import javax.swing.ImageIcon;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+import forge.card.CardRules;
+import forge.card.CardRulesPredicates;
+import forge.gui.deckeditor.views.VCardCatalog;
+import forge.gui.deckeditor.views.VCurrentDeck;
+import forge.gui.toolbox.FSkin;
+import forge.item.PaperCard;
+import forge.item.InventoryItem;
+import forge.item.ItemPoolView;
+import forge.util.Aggregates;
+import forge.util.TextUtil;
+
+
+/**
+ * Static methods for working with top-level editor methods,
+ * included but not limited to preferences IO, icon generation,
+ * and stats analysis.
+ *
+ *
+ * (S at beginning of class name denotes a static factory.)
+ *
+ */
+public final class SCardListViewUtil {
+ /** An enum to encapsulate metadata for the stats/filter objects. */
+ public static enum StatTypes {
+ TOTAL (FSkin.ZoneImages.ICO_HAND, null, 0),
+ WHITE (FSkin.ManaImages.IMG_WHITE, CardRulesPredicates.Presets.IS_WHITE, 1),
+ BLUE (FSkin.ManaImages.IMG_BLUE, CardRulesPredicates.Presets.IS_BLUE, 1),
+ BLACK (FSkin.ManaImages.IMG_BLACK, CardRulesPredicates.Presets.IS_BLACK, 1),
+ RED (FSkin.ManaImages.IMG_RED, CardRulesPredicates.Presets.IS_RED, 1),
+ GREEN (FSkin.ManaImages.IMG_GREEN, CardRulesPredicates.Presets.IS_GREEN, 1),
+ COLORLESS (FSkin.ManaImages.IMG_COLORLESS, CardRulesPredicates.Presets.IS_COLORLESS, 1),
+ MULTICOLOR (FSkin.EditorImages.IMG_MULTI, CardRulesPredicates.Presets.IS_MULTICOLOR, 1),
+
+ PACK (FSkin.EditorImages.IMG_PACK, null, 2),
+ LAND (FSkin.EditorImages.IMG_LAND, CardRulesPredicates.Presets.IS_LAND, 2),
+ ARTIFACT (FSkin.EditorImages.IMG_ARTIFACT, CardRulesPredicates.Presets.IS_ARTIFACT, 2),
+ CREATURE (FSkin.EditorImages.IMG_CREATURE, CardRulesPredicates.Presets.IS_CREATURE, 2),
+ ENCHANTMENT (FSkin.EditorImages.IMG_ENCHANTMENT, CardRulesPredicates.Presets.IS_ENCHANTMENT, 2),
+ PLANESWALKER (FSkin.EditorImages.IMG_PLANESWALKER, CardRulesPredicates.Presets.IS_PLANESWALKER, 2),
+ INSTANT (FSkin.EditorImages.IMG_INSTANT, CardRulesPredicates.Presets.IS_INSTANT, 2),
+ SORCERY (FSkin.EditorImages.IMG_SORCERY, CardRulesPredicates.Presets.IS_SORCERY, 2);
+
+ public final ImageIcon img;
+ public final Predicate predicate;
+ public final int group;
+
+ StatTypes(FSkin.SkinProp prop, Predicate pred, int grp) {
+ img = new ImageIcon(FSkin.getImage(prop, 18, 18));
+ predicate = pred;
+ group = grp;
+ }
+
+ public String toLabelString() {
+ if (this == PACK) {
+ return "Card packs and prebuilt decks";
+ }
+ return TextUtil.enumToLabel(this) + " cards";
+ }
+ }
+
+ /**
+ * Divides X by Y, multiplies by 100, rounds, returns.
+ *
+ * @param x0 Numerator (int)
+ * @param y0 Denominator (int)
+ * @return rounded result (int)
+ */
+ public static int calculatePercentage(final int x0, final int y0) {
+ return (int) Math.round((double) (x0 * 100) / (double) y0);
+ }
+
+ private static final Predicate