/** * eAdventure (formerly <e-Adventure> and <e-Game>) is a research project of the * <e-UCM> research group. * * Copyright 2005-2010 <e-UCM> research group. * * You can access a list of all the contributors to eAdventure at: * http://e-adventure.e-ucm.es/contributors * * <e-UCM> is a research group of the Department of Software Engineering * and Artificial Intelligence at the Complutense University of Madrid * (School of Computer Science). * * C Profesor Jose Garcia Santesmases sn, * 28040 Madrid (Madrid), Spain. * * For more info please visit: <http://e-adventure.e-ucm.es> or * <http://www.e-ucm.es> * * **************************************************************************** * * This file is part of eAdventure, version 2.0 * * eAdventure is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * eAdventure 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with eAdventure. If not, see <http://www.gnu.org/licenses/>. */ package es.eucm.ead.editor.view.generic.table; import es.eucm.ead.editor.control.Command; import es.eucm.ead.editor.control.commands.ChangeFieldCommand; import es.eucm.ead.editor.control.commands.MapCommand; import es.eucm.ead.editor.model.nodes.DependencyNode; import es.eucm.ead.editor.view.generic.AbstractOption; import es.eucm.ead.editor.view.generic.accessors.Accessor; import es.eucm.ead.editor.view.generic.accessors.IntrospectingAccessor; import es.eucm.ead.editor.view.generic.accessors.MapAccessor; import es.eucm.ead.editor.view.generic.table.TableSupport.AbstractRowTableModel; import es.eucm.ead.editor.view.generic.table.TableSupport.DeleteButtonWidget; import es.eucm.ead.editor.view.generic.table.TableSupport.DeleteIt; import es.eucm.ead.editor.view.generic.table.TableSupport.MoveButtonWidget; import es.eucm.ead.editor.view.generic.table.TableSupport.MoveIt; import es.eucm.ead.editor.view.generic.table.TableSupport.Row; import es.eucm.ead.model.elements.extra.EAdMap; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import org.jdesktop.swingx.JXTable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An option that allows a map of elements to be manipulated. Conceptually very * similar to manipulating a list. * * @author mfreire * @param <V> value-type (for underlying map) */ public class MapOption<V> extends AbstractOption<EAdMap<V>> implements TableLikeControl<V, String> { static private Logger logger = LoggerFactory.getLogger(MapOption.class); private JPanel controlPanel; private JXTable tableControl; private JButton chooseMoreButton; private MapTableModel tableModel; private final Class<?> contentClass; public MapOption(String title, String toolTipText, Object object, String fieldName, Class<?> contentClass, DependencyNode... changed) { super(title, toolTipText, new IntrospectingAccessor<EAdMap<V>>(object, fieldName), changed); this.contentClass = contentClass; } @SuppressWarnings("unchecked") public ColumnSpec<V, String>[] getKeyColumns() { return (ColumnSpec<V, String>[]) new ColumnSpec[] { new ColumnSpec<V, String>( "Key", String.class, false, -1) { @Override public Object getValue(Row<V, String> row, int columnIndex) { return row.getKey(); } } }; } @SuppressWarnings("unchecked") public ColumnSpec<V, String>[] getValueColumns() { return (ColumnSpec<V, String>[]) new ColumnSpec[] { new ColumnSpec( "Value", contentClass, false, -1) }; } /** * Model used to represent the map. Looks directly at oldValue; which must * always be updated. */ private class MapTableModel extends AbstractRowTableModel<V, String> { private final HashMap<String, Integer> keysToRows = new HashMap<String, Integer>(); @SuppressWarnings("unchecked") public MapTableModel() { super(MapOption.this); ColumnSpec<V, String> upDown = new ColumnSpec<V, String>("", MoveIt.class, true, 16); upDown.setEditor(new MoveButtonWidget(MapOption.this)); upDown.setRenderer(new MoveButtonWidget(MapOption.this)); ColumnSpec<V, String> delete = new ColumnSpec<V, String>("", DeleteIt.class, true, 20); delete.setEditor(new DeleteButtonWidget(MapOption.this)); delete.setRenderer(new DeleteButtonWidget(MapOption.this)); ColumnSpec<V, String>[] keys = (ColumnSpec<V, String>[]) getKeyColumns(); ColumnSpec<V, String>[] values = (ColumnSpec<V, String>[]) getValueColumns(); int totalColCount = keys.length + values.length + 1; cols = (ColumnSpec<V, String>[]) new ColumnSpec[totalColCount]; System.arraycopy(keys, 0, cols, 0, keys.length); System.arraycopy(values, 0, cols, keys.length, values.length); cols[cols.length - 1] = delete; if (logger.isDebugEnabled()) { int i = 0; for (ColumnSpec<V, String> c : cols) { logger.debug(" -- at col {}: {}{}", i++, c.getClass() .getSimpleName(), c.hashCode()); } } reindex(); } @Override @SuppressWarnings("unchecked") public void reindex() { rows = new Row[oldValue.size()]; keysToRows.clear(); Iterator<Map.Entry<String, V>> it = oldValue.entrySet().iterator(); for (int i = 0; it.hasNext(); i++) { rows[i] = new Row<V, String>(it.next()); keysToRows.put(rows[i].getKey(), i); } } @Override @SuppressWarnings("unchecked") public void setValueAt(Object value, int rowIndex, int columnIndex) { Row<V, String> r = rows[rowIndex]; Accessor a = cols[columnIndex].getAccessor(r, columnIndex); if (a.getSource() == r) { // direct change if (columnIndex == 0) { // changing the key if (oldValue.containsKey((String) value)) { // refuse to change value - would overwrite existing logger.warn("Refusing to allow user to overwrite key"); } else { performCommand(new MapCommand.ChangeKeyInMap<V>( oldValue, r.getKey(), (String) value, changed)); } } else { // changing a map value a = new MapAccessor(oldValue, r.getKey()); performCommand(new ChangeFieldCommand(value, a, changed)); } } else { // changing a field within the key or value performCommand(new ChangeFieldCommand(value, a, changed)); } } } @Override protected JComponent createControl() { oldValue = accessor.read(); tableModel = new MapTableModel(); tableControl = new JXTable(tableModel); for (int i = 0; i < tableModel.cols.length; i++) { ColumnSpec<V, String> c = tableModel.cols[i]; if (c.getRenderer() != null) { tableControl.getColumn(i).setCellRenderer(c.getRenderer()); } if (c.getEditor() != null) { tableControl.getColumn(i).setCellEditor(c.getEditor()); } if (c.getWidth() != -1) { tableControl.getColumn(i).setMinWidth(c.getWidth()); tableControl.getColumn(i).setMaxWidth(c.getWidth()); } } tableControl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); tableControl.setColumnControlVisible(false); tableControl.setSortable(false); tableControl.setAutoResizeMode(JXTable.AUTO_RESIZE_ALL_COLUMNS); tableControl.setRowHeight(32); tableControl.setColumnMargin(5); chooseMoreButton = new JButton("+"); chooseMoreButton.setToolTipText(Messages.options_table_add); chooseMoreButton.setPreferredSize(new Dimension(50, 16)); chooseMoreButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { V value = chooseElementToAdd(); String key = chooseKeyToAdd(); if (value != null) { add(value, key); } } }); controlPanel = new JPanel(new BorderLayout()); JScrollPane tableScroll = new JScrollPane(tableControl); tableScroll.setMinimumSize(new Dimension(0, 120)); tableScroll.setPreferredSize(new Dimension(0, 120)); controlPanel.add(tableScroll, BorderLayout.CENTER); controlPanel.add(chooseMoreButton, BorderLayout.SOUTH); return controlPanel; } @Override public EAdMap<V> getControlValue() { return accessor.read(); } @Override protected void setControlValue(EAdMap<V> newValue) { tableModel.fireTableDataChanged(); } @Override public void remove(String key) { V o = oldValue.get(key); logger.info("Removing {} (at {})", new Object[] { o, key }); Command c = new MapCommand.RemoveFromMap<V>(oldValue, key, changed); performCommand(c); } // FIXME - unimplemented @Override public V chooseElementToAdd() { logger.info("User wants to CHOOSE something to ADD! Madness!!"); return null; } /** * Launches UI prompt to add a key to a list element */ @Override public String chooseKeyToAdd() { logger.info("User wants to CHOOSE a KEY to ADD something! Madness!!"); return null; } @Override public void add(V added, String key) { logger.info("Adding {}", oldValue); Command c = new MapCommand.AddToMap<V>(oldValue, added, key, changed); performCommand(c); } /** * Moves an object one position up. Triggered either externally or via * button-click. */ @Override public void moveUp(String index) { throw new UnsupportedOperationException(); } /** * Removes an object from the list. Triggered either externally or via * button-click. */ @Override public void moveDown(String index) { throw new UnsupportedOperationException(); } /** * Returns the key for a given row */ @Override public String keyForRow(int row) { return tableModel.keyForRow(row); } private void performCommand(Command c) { manager.performCommand(c); } /** * Consider contents to have changed, even if the list-reference does not * change. * * @param oldValue * @param newValue * @return */ @Override protected boolean changeConsideredRelevant(EAdMap<V> oldValue, EAdMap<V> newValue) { return true; } }