package com.hero.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.math.BigDecimal;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;

import com.hero.HeroDesigner;
import com.hero.objects.Adder;
import com.hero.objects.GenericObject;
import com.hero.objects.disads.Disadvantage;
import com.hero.objects.enhancers.Enhancer;
import com.hero.objects.martialarts.Maneuver;
import com.hero.objects.modifiers.Modifier;
import com.hero.objects.perks.Perk;
import com.hero.objects.powers.CompoundPower;
import com.hero.objects.powers.Power;
import com.hero.objects.skills.Skill;
import com.hero.objects.talents.MageSight;
import com.hero.objects.talents.Talent;
import com.hero.ui.dialog.GenericDialog;
import com.hero.ui.widgets.PopupMessage;
import com.hero.util.Constants;
import com.hero.util.Rounder;

/**
 * Copyright (c) 2000 - 2005, CompNet Design, Inc. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, is prohibited unless the following conditions are met: 1.
 * Express written consent of CompNet Design, Inc. is obtained by the developer.
 * 2. Redistributions must retain this copyright notice. THIS SOFTWARE IS
 * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @author CompNet Design, Inc.
 * @version $Revision$
 */

public abstract class GenericObjectList extends JInternalFrame {

	private class HeroHeaderRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = -8515500448995331098L;

		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {
			JLabel result = new JLabel();
			if (column == 0) {
				result.setText(column1);
				result.setHorizontalAlignment(SwingConstants.RIGHT);
				result.setHorizontalTextPosition(SwingConstants.RIGHT);
			} else if (column == 2) {
				result.setText(column3);
				result.setHorizontalAlignment(SwingConstants.CENTER);
				result.setHorizontalTextPosition(SwingConstants.RIGHT);
			} else {
				if (column2.equals("Disadvantage")) {
					result
							.setText(HeroDesigner.getActiveTemplate().is6E() ? "Complication"
									: "Disadvantage");
				} else {
					result.setText(column2);
				}
				result.setHorizontalAlignment(SwingConstants.LEFT);
				result.setHorizontalTextPosition(SwingConstants.RIGHT);
			}

			result.setFont(result.getFont().deriveFont(Font.BOLD));
			result.setBorder(BorderFactory.createRaisedBevelBorder());
			result.setOpaque(false);

			result.setVerticalAlignment(SwingConstants.CENTER);
			return result;
		}
	}

	private class HeroModel extends AbstractTableModel {

		private static final long serialVersionUID = 5084721192653813290L;

		@Override
		public void fireTableDataChanged() {
			table.setPreferredScrollableViewportSize(scroll.getViewport()
					.getSize());
			super.fireTableDataChanged();
			int count = -1;
			for (GenericObject o : objects) {
				count++;
				o.setPosition(count);
				Component comp = table.getCellRenderer(count, 1)
						.getTableCellRendererComponent(table,
								table.getValueAt(count, 1), false, false,
								count, 1);
				int height = comp.getPreferredSize().height;
				if (height < 10) {
					height = 20;
				}
				table.setRowHeight(count, height + 3);
			}
			if ((selectedIndex >= 0) && (objects.size() > 0)) {
				if (selectedIndex >= objects.size()) {
					selectedIndex = objects.size() - 1;
				}
				table.clearSelection();
				table.setRowSelectionInterval(selectedIndex, selectedIndex);
			} else {
				table.clearSelection();
			}
		}

		@Override
		public Class getColumnClass(int column) {
			return String.class;
		}

		public int getColumnCount() {
			return col3Present ? 3 : 2;
		}

		public int getRowCount() {
			return objects.size();
		}

		public Object getValueAt(int row, int column) {
			GenericObject o = objects.get(row);
			switch (column) {
			case 0:
				return o.getColumn1Output();
			case 1:
				return o.getTableColumn2Output(table.getColumnModel()
						.getColumn(1).getWidth());
			case 2:
				return o.getColumn3Output();
			default:
				return "";
			}
		}

		@Override
		public boolean isCellEditable(int row, int column) {
			if (column == 1)
				return true;
			else
				return false;
		}

		@Override
		public void setValueAt(Object value, int row, int column) {
			if (column == 1) {
				GenericObject o = objects.get(row);
				o.setTextOutput(value.toString());
			}
		}

	}

	private class HeroRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = -258890461916547952L;

		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {
			JLabel result = (JLabel) super.getTableCellRendererComponent(table,
					value, isSelected, hasFocus, row, column);
			if (column == 0) {
				result.setHorizontalAlignment(SwingConstants.RIGHT);
			} else if (column == 2) {
				result.setHorizontalAlignment(SwingConstants.CENTER);
			} else {
				result.setHorizontalAlignment(SwingConstants.LEFT);
				result.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 5));
			}

			result.setVerticalAlignment(SwingConstants.TOP);

			return result;
		}
	}

	private class HeroEditor extends DefaultCellEditor {
		private static final long serialVersionUID = -258890461916547952L;

		public HeroEditor() {
			super(new JTextField());
			setClickCountToStart(2);
		}

		@Override
		public Component getTableCellEditorComponent(JTable pTable,
				Object pValue, boolean pIsSelected, int pRow, int pColumn) {
			GenericObject o = objects.get(pRow);
			JComponent ret = (JComponent) super.getTableCellEditorComponent(pTable, o.getTextOutput(),
					pIsSelected, pRow, pColumn);
			ret.setBorder(BorderFactory.createEmptyBorder(1,1,1,5));
			return ret;
		}

	}

	public abstract class SelectionListener implements ListSelectionListener {
		public abstract void selectionChanged(GenericObject selection);

		public void valueChanged(ListSelectionEvent e) {
			if (table.getSelectedRow() >= 0) {
				selectionChanged(objects.get(table.getSelectedRow()));
			} else {
				selectionChanged(null);
			}
		}
	}

	/**
	 * @author Dan Simon A custom listener to use when you want to be notified
	 *         of any updates to the purchase list.
	 */
	public abstract class UpdateListener {
		public abstract void updatePerformed();
	}

	protected long checkIndicesTime;

	boolean col3Present;

	String column1;

	String column2;

	String column3;

	protected ActionListener copyListener;

	protected JMenuItem copyMI;

	protected ActionListener cutListener;

	protected JMenuItem cutMI;

	protected JMenuItem defineMI;

	JButton deleteBtn;

	protected JMenuItem deleteMI;

	JButton downBtn;

	protected JMenuItem downMI;

	JButton editBtn;

	protected JMenuItem editMI;

	boolean insertBefore = false;

	JButton insertBtn;

	long lastSet = -1;

	ArrayList<UpdateListener> listeners;

	protected HeroModel model;

	ArrayList<GenericObject> objects;

	protected ActionListener pasteListener;

	protected JMenuItem pasteMI;

	protected JMenuItem copyModsMI;
	protected JMenuItem cutModsMI;
	protected JMenuItem pasteModsMI;

	protected JPopupMenu popup;

	JScrollPane scroll;

	protected int selectedIndex = -1;

	protected JTable table;

	double thisTotal = -999999;

	double thisCarried = 0;

	double thisWeight = 0;

	protected long totalCostCalcTime;

	String type;

	JButton upBtn;

	protected JMenuItem upMI;

	private boolean locked;

	/**
	 * @param type
	 *            A String for displaying the "type" of ability being tracked
	 *            (e.g. "Powers")
	 * @param col1
	 *            The label to use for the column1 header.
	 * @param col2
	 *            The label to use for the column2 header.
	 * @param col3
	 *            The label to use for the column3 header. If null, column3 will
	 *            be omitted.
	 * @param data
	 *            The data Vector to track changes in.
	 */
	public GenericObjectList(String type, String col1, String col2,
			String col3, ArrayList<GenericObject> data) {
		super(type, false, false, false, false);
		this.type = type;
		column1 = col1;
		column2 = col2;
		column3 = col3;
		if ((column3 == null) || (column3.trim().length() == 0)) {
			col3Present = false;
		} else {
			col3Present = true;
		}
		objects = data;
		listeners = new ArrayList<UpdateListener>();
		initWidgets();
		initListeners();
		layoutComponent();
		pack();
		setVisible(true);
		toFront();
		setFrameIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(
				new byte[0], 0, 0)));
	}

	/**
	 * Adds the specified object into the purchase list at the selected index.
	 * If the item is a list, then the contents of the list will also be added.
	 * 
	 * @param o
	 */
	public void addObject(GenericObject o) {
		int index = getInsertionIndex();
		int parentIndex = index - 1;
		if (parentIndex < 0) {
			parentIndex = 0;
		}
		if (insertBefore) {
			parentIndex = index - 1;
		}
		if (parentIndex >= objects.size()) {
			parentIndex = objects.size() - 1;
		}
		if ((parentIndex >= 0) && (objects.size() > 0)) {
			GenericObject parent = objects.get(parentIndex);
			if (parent instanceof com.hero.objects.List) {
				com.hero.objects.List list = (com.hero.objects.List) parent;
				if (!list.objectAllowed(o)) { // not allowed...
					// first, clear out the parent...
					o.setParent(null);
					if (insertBefore) {
						index -= 1;
					} else {
						// index++;
						index += list.getObjects().size();
					}
					if (list.getRejectionMessage().trim().length() > 0) {
						JComponent comp = this;
						PopupMessage popup = PopupMessage.getInstance(
								HeroDesigner.getAppFrame(), comp, list
										.getRejectionMessage(), true);
						popup.setVisible(true);
					}
				} else {
					list.getObjects().add(0, o);
					o.setParent(list);
				}
			} else if (parent.getParentList() != null) {
				com.hero.objects.List list = parent.getParentList();
				if (!list.objectAllowed(o)) { // not
					// allowed....reposition
					o.setParent(null);
					if (insertBefore) {
						index = index
								- (parent.getPosition() - list.getPosition())
								- 1;
					} else {
						index = list.getPosition() + list.getObjects().size()
								+ 1;
					}
					if (list.getRejectionMessage().trim().length() > 0) {
						JComponent comp = this;
						PopupMessage popup = PopupMessage.getInstance(
								HeroDesigner.getAppFrame(), comp, list
										.getRejectionMessage(), true);
						popup.setVisible(true);
					}
				} else {
					int insertAt = index - list.getPosition();
					if (insertAt >= list.getObjects().size()) {
						list.getObjects().add(o);
					} else if (insertAt < 0) {
						insertAt = 0;
					} else {
						list.getObjects().add(insertAt, o);
					}
					o.setParent(list);
				}
			}
		}
		o.setPosition(index);
		// now make room for the new object...
		int roomToMake = 1;
		if (o instanceof com.hero.objects.List) {
			com.hero.objects.List l = (com.hero.objects.List) o;
			roomToMake += l.getObjects().size();
		}
		for (GenericObject ob : objects) {
			if (ob.getPosition() >= index) {
				ob.setPosition(ob.getPosition() + roomToMake);
			}
		}
		if (index < objects.size()) {
			if (o instanceof com.hero.objects.List) {
				com.hero.objects.List l = (com.hero.objects.List) o;
				for (int i = l.getObjects().size() - 1; i >= 0; i--) {
					objects.add(index, l.getObjects().get(i));
				}
			}
			objects.add(index, o);
		} else {
			objects.add(o);
			if (o instanceof com.hero.objects.List) {
				com.hero.objects.List l = (com.hero.objects.List) o;
				for (GenericObject ch : l.getObjects()) {
					ch.getRealCostPreList(); // safety...just to ensure
					// enhancer stuff is taken
					// care of
					objects.add(ch);
				}
			}
		}
		selectedIndex = index;
		HeroDesigner.getActiveHero().setDirty(true);
		updateTotal();
	}

	/**
	 * Adds a selection listener to the JTable that renders the objects.
	 * 
	 * @param listener
	 */
	public void addSelectionListener(SelectionListener listener) {
		table.getSelectionModel().addListSelectionListener(listener);
	}

	/**
	 * Adds an update listener to be notified of any changes to the purchase
	 * list.
	 * 
	 * @param listener
	 */
	public void addUpdateListener(UpdateListener listener) {
		listeners.add(listener);
	}

	protected boolean checkCopy() {
		return selectedIndex >= 0;
	}

	protected boolean checkCut() {
		return selectedIndex >= 0;
	}

	private boolean checkDown() {
		if ((selectedIndex < 0) || (selectedIndex >= objects.size())) {
			return false;
		}
		GenericObject o = objects.get(selectedIndex);
		if (o instanceof com.hero.objects.List) {
			int numObjects = ((com.hero.objects.List) o).getObjects().size();
			if (selectedIndex + numObjects < objects.size() - 1) {
				return true;
			} else {
				return false;
			}
		} else if (o.getParentList() != null) {
			return true;
		} else if (selectedIndex < objects.size() - 1) {
			return true;
		} else {
			return false;
		}
	}

	void checkIndices() {
		if ((checkIndicesTime > 0)
				&& (checkIndicesTime > HeroDesigner.lastEdit)) {
			return;
		}
		for (int i = objects.size() - 1; i >= 0; i--) {
			GenericObject o = objects.get(i);
			if ((o.getParentList() != null)
					&& !(o.getParentList() instanceof Enhancer)) {
				com.hero.objects.List list = o.getParentList();
				if (list.objectAllowed(o, false)) {
					// do nothing;
				} else { // push it off the bottom of the list...
					objects.remove(i);
					list.remove(o);
					o.setParent(null);
					if (list.getPosition() + list.getObjects().size() + 1 >= objects
							.size()) {
						objects.add(o);
					} else {
						objects.add(list.getPosition()
								+ list.getObjects().size() + 1, o);
					}
					// JComponent comp = this;
					JOptionPane.showMessageDialog(HeroDesigner.getAppFrame(),
							list.getRejectionMessage(),
							"Object not allowed in list",
							JOptionPane.WARNING_MESSAGE);
					o.setAppAdjusted(true);
					HeroDesigner.getActiveHero().setDirty(true);
				}
			}
		}

		// re-normalize (just to be safe)
		normalize();
	}

	protected boolean checkPaste() {
		return HeroDesigner.getCopyBuffer() != null;
	}
	
	protected boolean checkPasteMods() {
		return (HeroDesigner.getInstance().getCopyModBuffer() != null 
			&& HeroDesigner.getInstance().getCopyModBuffer().size()>0);
	}

	protected abstract boolean checkReplace(GenericObject replacement,
			GenericObject replacee);

	private boolean checkUp() {
		if (selectedIndex <= 0) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Cuts the current selection, copying it to the internal copy buffer and
	 * then deleting it from the purchase list.
	 */
	public void cutSelection() {
		locked = true;
		syncSelectedIndex();
		if (selectedIndex >= 0) {
			GenericObject obj = objects.get(selectedIndex);
			if (obj instanceof Adder) {
				locked = false;
				return;
			}
			if (obj.getMainPower() != null) {
				CompoundPower cp = obj.getMainPower();
				cp.getPowers().remove(obj);
			}
			if ((obj instanceof com.hero.objects.List)
					&& (((com.hero.objects.List) obj).getObjects().size() > 0)) {
				com.hero.objects.List list = (com.hero.objects.List) obj;
				for (int i = 0; i < list.getObjects().size(); i++) {
					objects.remove(list.getObjects().get(i));
				}
			} else if (obj.getParentList() != null) {
				com.hero.objects.List list = obj.getParentList();
				list.remove(obj);
			}
			int selectedIndex = objects.indexOf(obj);
			objects.remove(obj);
			if (selectedIndex >= objects.size()) {
				selectedIndex = objects.size() - 1;
			}
		}
		HeroDesigner.getActiveHero().setDirty(true);
		// checkIndices();
		updateTotal();
		locked = false;
	}

	protected JPanel getButtonPanel() {
		JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
		buttons.add(downBtn);
		buttons.add(editBtn);
		buttons.add(insertBtn);
		buttons.add(deleteBtn);
		buttons.add(upBtn);
		buttons.setOpaque(false);
		return buttons;
	}

	protected ActionListener getCopyAction() {
		return copyListener;
	}

	protected ActionListener getCutAction() {
		return cutListener;
	}

	protected int getInsertionIndex() {
		syncSelectedIndex();
		int ret = 0;
		if ((selectedIndex < 0) || (selectedIndex >= objects.size())) {
			if (insertBefore) {
				ret = 0;
			} else {
				ret = objects.size();
			}
		} else {
			if (insertBefore) {
				ret = selectedIndex;
			} else {
				ret = selectedIndex + 1;
			}
		}
		if (ret < 0) {
			ret = 0;
		}
		if (ret > objects.size()) {
			ret = objects.size();
		}
		return ret;
	}

	protected com.hero.objects.List getInsertionParent() {
		syncSelectedIndex();
		com.hero.objects.List ret = null;
		if (selectedIndex >= 0) {
			GenericObject selectedObject = objects.get(selectedIndex);
			ret = selectedObject.getParentList();
			if ((selectedObject instanceof com.hero.objects.List)
					&& !insertBefore) {
				ret = (com.hero.objects.List) selectedObject;
			} else if (selectedObject.getParentList() != null) {
				ret = selectedObject.getParentList();
			}
		}
		return ret;
	}

	protected ArrayList<GenericObject> getObjects() {
		return objects;
	}

	protected ActionListener getPasteAction() {
		return pasteListener;
	}

	protected JPopupMenu getPopupMenu() {
		return popup;
	}

	/**
	 * Returns the JScrollPane that contains the main table used to render the
	 * purchase list.
	 * 
	 * @return
	 */
	public JScrollPane getScroll() {
		return scroll;
	}

	/**
	 * Returns the currently selected index. -1 for none.
	 * 
	 * @return
	 */
	public int getSelectedIndex() {
		syncSelectedIndex();
		return selectedIndex;
	}

	/**
	 * Returns the currently selected object. Null if none.
	 * 
	 * @return
	 */
	public GenericObject getSelection() {
		syncSelectedIndex();
		if ((selectedIndex < 0) || (selectedIndex >= objects.size())) {
			return null;
		} else {
			return objects.get(selectedIndex);
		}
	}

	protected void initListeners() {
		upMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				upBtn.doClick();
			}
		});
		downMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				downBtn.doClick();
			}
		});
		editMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				editBtn.doClick();
			}
		});
		deleteMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				deleteBtn.doClick();
			}
		});
		defineMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				GenericObject obj = getSelection();
				if (obj == null) {
					return;
				}
				String definition = obj.getNotes();
				if ((definition == null) || (definition.trim().length() == 0)) {
					definition = obj.getDefinition();
				}
				if (definition == null) {
					return;
				}
				if (definition.trim().length() == 0) {
					return;
				}
				PopupMessage popup = PopupMessage.getInstance(HeroDesigner
						.getAppFrame(), table, definition, false);
				popup.setVisible(true);
			}
		});
		table.addComponentListener(new ComponentListener() {
			public void componentHidden(ComponentEvent e) {

			}

			public void componentMoved(ComponentEvent e) {

			}

			public void componentResized(ComponentEvent e) {
				model.fireTableDataChanged();
			}

			public void componentShown(ComponentEvent e) {
				model.fireTableDataChanged();
			}
		});
		table.getSelectionModel().addListSelectionListener(
				new ListSelectionListener() {
					public void valueChanged(ListSelectionEvent e) {
						if (locked) {
							return;
						}
						synchronized (table) {
							locked = true;
							setSelectedIndex(table.getSelectedRow(), false);
							lastSet = System.currentTimeMillis();
							locked = false;
						}
					}
				});
		table.addMouseListener(new MouseListener() {
			public void mouseClicked(MouseEvent e) {
				if (locked) {
					return;
				}
				if (e.isPopupTrigger() && (popup != null)) {
					if (!locked) {
						locked = true;
						synchronized (table) {
							selectedIndex = -1;
							setSelectedIndex(table.rowAtPoint(e.getPoint()),
									false);
						}
						pasteMI.setEnabled(checkPaste());
						pasteModsMI.setEnabled(getSelection() != null && checkPasteMods());
						if (getSelection() != null
								&& getSelection().getAllAssignedModifiers() != null
								&& getSelection().getAllAssignedModifiers().size() > 0) {
							cutModsMI.setEnabled(true);
							copyModsMI.setEnabled(true);
						} else {
							cutModsMI.setEnabled(false);
							copyModsMI.setEnabled(true);
						}
						editMI.setEnabled(editBtn.isEnabled());
						deleteMI.setEnabled(deleteBtn.isEnabled());
						upMI.setEnabled(upBtn.isEnabled());
						downMI.setEnabled(downBtn.isEnabled());
						defineMI.setEnabled((selectedIndex >= 0)
								&& (objects.get(selectedIndex).getDefinition()
										.trim().length() > 0));
						table.repaint();
						if (table.getSelectedRow() >= 0) {
							popup.show(e.getComponent(), e.getX(), e.getY());
						}
						locked = false;
					}
					return;
				}
				if (e.getClickCount() == 2) {
					synchronized (table) {
						selectedIndex = -1;
						setSelectedIndex(table.getSelectedRow(), true);
					}
				} else if ((e.getButton() == MouseEvent.BUTTON1)
						&& (System.currentTimeMillis() - lastSet > 1000)) {
					synchronized (table) {
						if (table.rowAtPoint(e.getPoint()) == selectedIndex) {
							table.clearSelection();
							upBtn.setEnabled(false);
							downBtn.setEnabled(false);
							editBtn.setEnabled(false);
							deleteBtn.setEnabled(false);
							if (insertBefore) {
								insertBtn.setText("Insert At Top");
							} else {
								insertBtn.setText("Insert At End");
							}
							selectedIndex = -1;
						}
					}
				}
			}

			public void mouseEntered(MouseEvent e) {
			}

			public void mouseExited(MouseEvent e) {
			}

			public void mousePressed(MouseEvent e) {
				if (locked) {
					return;
				}
				if (e.isPopupTrigger() && (popup != null)) {
					if (!locked) {
						locked = true;
						synchronized (table) {
							selectedIndex = -1;
							setSelectedIndex(table.rowAtPoint(e.getPoint()),
									false);
						}
						pasteMI.setEnabled(checkPaste());
						pasteModsMI.setEnabled(getSelection() != null && checkPasteMods());
						if (getSelection() != null
								&& getSelection().getAllAssignedModifiers() != null
								&& getSelection().getAllAssignedModifiers().size() > 0) {
							cutModsMI.setEnabled(true);
							copyModsMI.setEnabled(true);
						} else {
							cutModsMI.setEnabled(false);
							copyModsMI.setEnabled(true);
						}
						editMI.setEnabled(editBtn.isEnabled());
						deleteMI.setEnabled(deleteBtn.isEnabled());
						upMI.setEnabled(upBtn.isEnabled());
						downMI.setEnabled(downBtn.isEnabled());
						defineMI.setEnabled((selectedIndex >= 0)
								&& (objects.get(selectedIndex).getDefinition()
										.trim().length() > 0));
						table.repaint();
						if (table.getSelectedRow() >= 0) {
							popup.show(e.getComponent(), e.getX(), e.getY());
						}
						locked = false;
					}
					return;
				}
			}

			public void mouseReleased(MouseEvent e) {
				if (locked) {
					return;
				}
				if (e.isPopupTrigger() && (popup != null)) {
					if (!locked) {
						locked = true;
						synchronized (table) {
							selectedIndex = -1;
							setSelectedIndex(table.rowAtPoint(e.getPoint()),
									false);
						}
						pasteMI.setEnabled(checkPaste());
						pasteModsMI.setEnabled(getSelection() != null && checkPasteMods());
						if (getSelection() != null
								&& getSelection().getAllAssignedModifiers() != null
								&& getSelection().getAllAssignedModifiers().size() > 0) {
							cutModsMI.setEnabled(true);
							copyModsMI.setEnabled(true);
						} else {
							cutModsMI.setEnabled(false);
							copyModsMI.setEnabled(true);
						}
						editMI.setEnabled(editBtn.isEnabled());
						deleteMI.setEnabled(deleteBtn.isEnabled());
						upMI.setEnabled(upBtn.isEnabled());
						downMI.setEnabled(downBtn.isEnabled());
						defineMI.setEnabled((selectedIndex >= 0)
								&& (objects.get(selectedIndex).getDefinition()
										.trim().length() > 0));
						table.repaint();
						if (table.getSelectedRow() >= 0) {
							popup.show(e.getComponent(), e.getX(), e.getY());
						}
						locked = false;
					}
					return;
				}
			}
		});
		table.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.isControlDown()) {
					if (e.getKeyCode() == KeyEvent.VK_C) {
						if (getSelection() != null) {
							copyMI.doClick();
						}
					}
					if (e.getKeyCode() == KeyEvent.VK_V) {
						if (HeroDesigner.getCopyBuffer() != null) {
							pasteMI.setEnabled(checkPaste());
							pasteMI.doClick();
						}
					}
					if (e.getKeyCode() == KeyEvent.VK_X) {
						if (getSelection() != null) {
							cutMI.doClick();
						}
					}
				} else if (e.getKeyChar() == KeyEvent.VK_ENTER) {
					synchronized (table) {
						setSelectedIndex(table.getSelectedRow(), true);
					}
				} else if (e.getKeyCode() == KeyEvent.VK_DELETE) {
					deleteBtn.doClick();
				} else if ((e.getKeyCode() == KeyEvent.VK_MINUS)
						|| (e.getKeyCode() == KeyEvent.VK_PLUS)
						|| (e.getKeyCode() == KeyEvent.VK_EQUALS)) {
					if (getSelection() != null) {
						GenericObject obj = getSelection();
						if ((obj.getLevelCost() != 0)
								&& (obj.getLevelValue() != 0)) {
							if ((e.getKeyCode() == KeyEvent.VK_MINUS)
									&& (obj.getLevels() > obj.getMinimumLevel())) {

								long id = obj.getID();
								GenericObject obj2 = obj.clone();
								obj2.setID(id); // maintain the id during edit
								obj2.setLevels(obj.getLevels() - 1);
								obj2.setMainPower(obj.getMainPower());
								if (obj.getMainPower() != null) {
									obj2.setParent(obj.getMainPower()
											.getParentList());
								} else {
									obj2.setParent(obj.getParentList());
								}
								if (!checkReplace(obj2, obj)) {
									locked = false;
									return;
								}
								objects.set(selectedIndex, obj2);
								if (obj2 instanceof com.hero.objects.List) {
									com.hero.objects.List list = (com.hero.objects.List) obj2;
									ArrayList<GenericObject> checks = list
											.getObjects();
									for (GenericObject go : checks) {
										go.setParent(list);
									}
								} else if (obj2.getParentList() != null) {
									com.hero.objects.List list = obj2
											.getParentList();
									if (list.getObjects().indexOf(obj) >= 0) {
										list.getObjects().set(
												list.getObjects().indexOf(obj),
												obj2);
									}
								}
								checkIndices();
								HeroDesigner.getActiveHero().setDirty(true);
								updateTotal();

								HeroDesigner.getActiveHero().setDirty(true);
								updateTotal();
							} else if (((e.getKeyCode() == KeyEvent.VK_PLUS) || (e
									.getKeyCode() == KeyEvent.VK_EQUALS))
									&& (obj.getLevels() < obj.getMaxLevel())) {
								long id = obj.getID();
								GenericObject obj2 = obj.clone();
								obj2.setID(id); // maintain the id during edit
								obj2.setLevels(obj.getLevels() + 1);
								obj2.setMainPower(obj.getMainPower());
								if (obj.getMainPower() != null) {
									obj2.setParent(obj.getMainPower()
											.getParentList());
								} else {
									obj2.setParent(obj.getParentList());
								}
								if (!checkReplace(obj2, obj)) {
									locked = false;
									return;
								}
								objects.set(selectedIndex, obj2);
								if (obj2 instanceof com.hero.objects.List) {
									com.hero.objects.List list = (com.hero.objects.List) obj2;
									ArrayList<GenericObject> checks = list
											.getObjects();
									for (GenericObject go : checks) {
										go.setParent(list);
									}
								} else if (obj2.getParentList() != null) {
									com.hero.objects.List list = obj2
											.getParentList();
									if (list.getObjects().indexOf(obj) >= 0) {
										list.getObjects().set(
												list.getObjects().indexOf(obj),
												obj2);
									}
								}
								checkIndices();
								HeroDesigner.getActiveHero().setDirty(true);
								updateTotal();

								HeroDesigner.getActiveHero().setDirty(true);
								updateTotal();
							}
						}
					}
				}
			}
		});

		insertBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				syncSelectedIndex();
				if (!insertBefore) {
					if (selectedIndex >= 0) {
						insertBtn.setText("Insert Before");
					} else {
						insertBtn.setText("Insert At Top");
					}
					insertBefore = true;
				} else {
					if (selectedIndex >= 0) {
						insertBtn.setText("Insert After");
					} else {
						insertBtn.setText("Insert At End");
					}
					insertBefore = false;
				}
			}
		});
		downBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				locked = true;
				if ((selectedIndex >= 0)
						&& ((selectedIndex < objects.size() - 1) || (objects
								.get(selectedIndex).getParentList() != null))) {
					GenericObject sel = objects.get(selectedIndex);
					if (sel instanceof com.hero.objects.List) {
						com.hero.objects.List list = (com.hero.objects.List) sel;
						GenericObject next = objects.get(selectedIndex
								+ list.getObjects().size() + 1);
						int indexIncrease = 1 + list.getObjects().size() + 1;
						if (next instanceof com.hero.objects.List) {
							com.hero.objects.List list2 = (com.hero.objects.List) next;
							indexIncrease += list2.getObjects().size();
						}
						int numToRemove = 1 + list.getObjects().size();
						indexIncrease -= numToRemove;
						for (int i = 0; i < numToRemove; i++) {
							objects.remove(selectedIndex);
						}
						for (int i = list.getObjects().size() - 1; i >= 0; i--) {
							objects.add(selectedIndex + indexIncrease, list
									.getObjects().get(i));
						}
						objects.add(selectedIndex + indexIncrease, list);
						selectedIndex += indexIncrease;
					} else {
						boolean increaseIndex = true;
						if (selectedIndex < objects.size() - 1) {
							GenericObject next = objects.get(selectedIndex + 1);
							// first remove it from its current list (if
							// necessary)...
							if ((next.getParentList() == null)
									&& (sel.getParentList() != null)) { // next
								// item
								// is
								// outside
								// of
								// list
								com.hero.objects.List list = sel
										.getParentList();
								list.remove(sel);
								sel.setParent(null);
								increaseIndex = false;
							} else if (next instanceof com.hero.objects.List) {
								com.hero.objects.List list = (com.hero.objects.List) next;
								if (list.objectAllowed(sel)) {
									ArrayList<GenericObject> listObs = ((com.hero.objects.List) next)
											.getObjects();
									listObs.add(0, sel);
									sel.setParent((com.hero.objects.List) next);
								} else {
									sel.setParent(null);
									objects.remove(selectedIndex);
									int index = selectedIndex
											+ list.getObjects().size() + 1;
									if (index >= objects.size()) {
										objects.add(sel);
									} else {
										objects.add(index, sel);
									}
									selectedIndex = index;
									increaseIndex = false;
									if (list.getRejectionMessage().trim()
											.length() > 0) {
										PopupMessage popup = PopupMessage
												.getInstance(
														HeroDesigner
																.getAppFrame(),
														downBtn,
														list
																.getRejectionMessage(),
														true);
										popup.setVisible(true);
									}
								}
							}
						} else {
							sel.getParentList().remove(sel);
							sel.setParent(null);
							increaseIndex = false;
						}
						if (increaseIndex) {
							objects.remove(selectedIndex);
							objects.add(selectedIndex + 1, sel);
							selectedIndex++;
						}
					}
					HeroDesigner.getActiveHero().setDirty(true);
				}
				upBtn.setEnabled(checkUp());
				downBtn.setEnabled(checkDown());
				updateTotal();
				locked = false;
			}
		});
		upBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				locked = true;
				if ((selectedIndex > 0) && (selectedIndex < objects.size())) {
					GenericObject sel = objects.get(selectedIndex);
					boolean decreaseIndex = true;
					if (selectedIndex > 0) {
						GenericObject prev = objects.get(selectedIndex - 1);
						if (sel instanceof com.hero.objects.List) {
							com.hero.objects.List list = (com.hero.objects.List) sel;
							int indexDecrease = 1;
							if (prev.getParentList() != null) {
								com.hero.objects.List list2 = prev
										.getParentList();
								indexDecrease += list2.getObjects().size();
							}
							int numToRemove = 1 + list.getObjects().size();
							for (int i = 0; i < numToRemove; i++) {
								if (selectedIndex < objects.size()) {
									objects.remove(selectedIndex);
								} else {
									objects.remove(objects.size() - 1);
								}
							}
							for (int i = list.getObjects().size() - 1; i >= 0; i--) {
								objects.add(selectedIndex - indexDecrease, list
										.getObjects().get(i));
							}
							objects.add(selectedIndex - indexDecrease, list);
							selectedIndex -= indexDecrease;
						} else {
							// first remove it from its current list (if
							// necessary)...
							if ((prev.getParentList() == null)
									&& (sel.getParentList() != null)) { // selection
								// is
								// at
								// top
								// of
								// list
								com.hero.objects.List list = sel
										.getParentList();
								list.remove(sel);
								sel.setParent(null);
							} else if ((prev.getParentList() != null)
									&& (sel.getParentList() == null)) { // selection
								// is
								// being
								// moved
								// into
								// list
								if (prev.getParentList().objectAllowed(sel)) {
									ArrayList<GenericObject> listObs = prev
											.getParentList().getObjects();
									listObs.add(sel);
									sel.setParent(prev.getParentList());
									decreaseIndex = false;
								} else {
									sel.setParent(null);
									objects.remove(selectedIndex);
									objects.add(Math.max(selectedIndex
											- 1
											- prev.getParentList().getObjects()
													.size(), 0), sel);
									selectedIndex = Math.max(selectedIndex
											- 1
											- prev.getParentList().getObjects()
													.size(), 0);
									decreaseIndex = false;
									PopupMessage popup = PopupMessage
											.getInstance(HeroDesigner
													.getAppFrame(), upBtn, prev
													.getParentList()
													.getRejectionMessage(),
													true);
									popup.setVisible(true);
								}
							} else if ((prev instanceof com.hero.objects.List)
									&& (sel.getParentList() == null)) { // add
								// it
								// into
								// the
								// list
								com.hero.objects.List list = (com.hero.objects.List) prev;
								if (list.objectAllowed(sel)) {
									list.getObjects().add(sel);
									sel.setParent(list);
									decreaseIndex = false;
								} else {
									sel.setParent(null);
									PopupMessage popup = PopupMessage
											.getInstance(HeroDesigner
													.getAppFrame(), upBtn, list
													.getRejectionMessage(),
													true);
									popup.setVisible(true);
								}
							}
							if (decreaseIndex) {
								objects.remove(selectedIndex);
								objects.add(selectedIndex - 1, sel);
								selectedIndex--;
							}
						}
					}
					HeroDesigner.getActiveHero().setDirty(true);
				}
				upBtn.setEnabled(checkUp());
				downBtn.setEnabled(checkDown());
				updateTotal();
				locked = false;
			}
		});
		editBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				locked = true;
				if ((selectedIndex >= 0) && (selectedIndex < objects.size())) {
					try {
						GenericObject obj = objects.get(selectedIndex);
						if (obj instanceof Adder) {
							locked = false;
							return;
						}
						if ((obj instanceof com.hero.objects.List)
								&& (obj.getTextOutput().trim().length() == 0)) {
							locked = false;
							return;
						}
						long id = obj.getID();
						GenericObject obj2 = obj.clone();
						obj2.setID(id); // maintain the id during edit

						obj2.setMainPower(obj.getMainPower());
						if (obj.getMainPower() != null) {
							obj2.setParent(obj.getMainPower().getParentList());
						} else {
							obj2.setParent(obj.getParentList());
						}

						GenericDialog dialog = obj2
								.getDialog(false,
										(objects == HeroDesigner
												.getActiveHero().getPowers())
												|| (objects == HeroDesigner
														.getActiveHero()
														.getEquipment()));
						dialog.setLocationRelativeTo(GenericObjectList.this);
						dialog.setVisible(true);
						if (dialog.okButtonClicked) {
							if (!checkReplace(obj2, obj)) {
								dialog.dispose();
								locked = false;
								return;
							}
							objects.set(selectedIndex, obj2);
							if (obj2 instanceof com.hero.objects.List) {
								com.hero.objects.List list = (com.hero.objects.List) obj2;
								ArrayList<GenericObject> checks = list
										.getObjects();
								for (GenericObject go : checks) {
									go.setParent(list);
								}
							} else if (obj2.getParentList() != null) {
								com.hero.objects.List list = obj2
										.getParentList();
								if (list.getObjects().indexOf(obj) >= 0) {
									list.getObjects().set(
											list.getObjects().indexOf(obj),
											obj2);
								}
							}
							checkIndices();
							HeroDesigner.getActiveHero().setDirty(true);
						} else if (dialog.deleteButtonClicked) {
							deleteBtn.doClick();
						} else if (dialog.cancelButtonClicked) {
							dialog.dispose();
							locked = false;
							return; // no layout necessary
						}
						dialog.dispose();
						updateTotal();
					} catch (Exception ex) {
						ex.printStackTrace();
					}
				}
				locked = false;
			}
		});
		deleteBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				locked = true;
				if ((selectedIndex >= 0) && (selectedIndex < objects.size())) {
					GenericObject obj = objects.get(selectedIndex);
					if (obj instanceof Adder) {
						locked = false;
						return;
					}
					String ability = "ability";
					if (obj instanceof com.hero.objects.List) {
						ability = "list";
					} else if (type.equals("Equipment")) {
						ability = "piece of equipment";
					} else if (obj instanceof Skill) {
						ability = "skill";
					} else if (obj instanceof Perk) {
						ability = "perk";
					} else if ((obj instanceof Talent)
							|| (obj instanceof MageSight)) {
						ability = "talent";
					} else if (obj instanceof Maneuver) {
						ability = "maneuver";
					} else if (obj instanceof Power) {
						ability = "power";
					} else if (obj instanceof Disadvantage) {
						if (HeroDesigner.getActiveTemplate().is6E())
							ability = "complication";
						else
							ability = "disadvantage";
					}
					if (HeroDesigner.getInstance().getPrefs().confirmDelete()) {
						int ret = JOptionPane.showConfirmDialog(
								GenericObjectList.this,
								"Are you sure you want to delete this "
										+ ability + "?", "Confirm Delete",
								JOptionPane.YES_NO_OPTION);
						if (ret != JOptionPane.YES_OPTION) {
							locked = false;
							return;
						}
					}
					if ((obj instanceof com.hero.objects.List)
							&& (((com.hero.objects.List) obj).getObjects()
									.size() > 0)) {
						int response = JOptionPane.showConfirmDialog(
								GenericObjectList.this,
								"Do you want to delete all items contained by "
										+ obj.getAlias() + "?");
						if (response == JOptionPane.CANCEL_OPTION) {
							locked = false;
							return;
						}
						if (response == JOptionPane.YES_OPTION) {
							com.hero.objects.List list = (com.hero.objects.List) obj;
							for (int i = list.getObjects().size() - 1; i >= 0; i--) {
								objects.remove(list.getObjects().get(i));
							}
						} else {
							com.hero.objects.List list = (com.hero.objects.List) obj;
							for (GenericObject child : list.getObjects()) {
								child.setParent(null);
							}
						}
					} else if (obj.getParentList() != null) {
						com.hero.objects.List list = obj.getParentList();
						list.remove(obj);
					}
					if (objects.size() > 1) {
						objects.remove(obj);
					} else {
						objects.clear();
					}
					selectedIndex--;
				}
				HeroDesigner.getActiveHero().setDirty(true);
				updateTotal();
				locked = false;
			}
		});
		setCutAction(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (selectedIndex >= 0) {
					GenericObject o = objects.get(selectedIndex);
					HeroDesigner.getInstance().setCopyBuffer(o.clone());
					cutSelection();
					if (HeroDesigner.getCopyBuffer() != null) {
						pasteMI.setEnabled(true);
					} else {
						pasteMI.setEnabled(false);
					}
				}
			}
		});
		setCopyAction(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (selectedIndex >= 0) {
					GenericObject o = objects.get(selectedIndex);
					String copy = o.getNamelessColumn2Output();
					if ((o.getName() != null)
							&& (o.getName().trim().length() > 0)) {
						copy = o.getName() + ":  " + copy;
					}
					StringSelection transferable = new StringSelection(copy);
					Toolkit.getDefaultToolkit().getSystemClipboard()
							.setContents(transferable, transferable);
					HeroDesigner.getInstance().setCopyBuffer(o.clone());
					if (HeroDesigner.getCopyBuffer() != null) {
						pasteMI.setEnabled(true);
					} else {
						pasteMI.setEnabled(false);
					}
				}
			}
		});
		copyModsMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (getSelection() != null) {
					HeroDesigner.getInstance().setCopyModBuffer(getSelection().getAllAssignedModifiers());
				}
			}
		});
		cutModsMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (getSelection() != null) {
					HeroDesigner.getInstance().setCopyModBuffer(getSelection().getAllAssignedModifiers());
					getSelection().setAssignedModifiers(new ArrayList<Modifier>());
					updateTotal();
				}
			}
		});
		pasteModsMI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (getSelection() != null) {
					ArrayList<Modifier> mods = HeroDesigner.getInstance().getCopyModBuffer();
					for (int i=0; i<mods.size(); i++) {
						Modifier mod = mods.get(i);
						if (mod.included(getSelection()).trim().length()==0) {
							getSelection().getAssignedModifiers().add(mod);
						}
					}
					updateTotal();
				}
			}
		});
		table.getColumnModel().addColumnModelListener(
				new TableColumnModelListener() {
					public void columnAdded(TableColumnModelEvent e) {

					}

					public void columnMarginChanged(ChangeEvent e) {

					}

					public void columnMoved(TableColumnModelEvent e) {

					}

					public void columnRemoved(TableColumnModelEvent e) {

					}

					public void columnSelectionChanged(ListSelectionEvent e) {

					}
				});
	}

	protected void initWidgets() {
		popup = new JPopupMenu("Edit Options");
		copyMI = new JMenuItem("Copy");
		cutMI = new JMenuItem("Cut");
		pasteMI = new JMenuItem("Paste");
		copyModsMI = new JMenuItem("Copy Modifiers");
		cutModsMI = new JMenuItem("Cut Modifiers");
		pasteModsMI = new JMenuItem("Paste Modifiers");
		editMI = new JMenuItem("Edit...");
		deleteMI = new JMenuItem("Delete");
		defineMI = new JMenuItem("Define");
		upMI = new JMenuItem("Move Up");
		downMI = new JMenuItem("Move Down");

		popup.add(cutMI);
		popup.add(copyMI);
		popup.add(pasteMI);
		popup.addSeparator();
		popup.add(copyModsMI);
		popup.add(cutModsMI);
		popup.add(pasteModsMI);
		popup.addSeparator();
		popup.add(upMI);
		popup.add(downMI);
		popup.addSeparator();
		popup.add(editMI);
		popup.add(defineMI);
		popup.addSeparator();
		popup.add(deleteMI);

		if (objects == null) {
			objects = new ArrayList<GenericObject>();
		}

		model = new HeroModel();
		table = new JTable();
		// table.setBackground(Color.white);
		table.setIntercellSpacing(new Dimension(3, 3));
		table.setModel(model);
		table.setShowGrid(true);
		table.setShowHorizontalLines(false);
		table.setShowVerticalLines(false);
		table.setCellSelectionEnabled(true);
		table.setRowSelectionAllowed(true);
		table.setColumnSelectionAllowed(false);
		table.getSelectionModel().setSelectionMode(
				ListSelectionModel.SINGLE_SELECTION);
		// ((DefaultCellEditor)
		// table.getDefaultEditor(String.class)).setClickCountToStart(1);
		table.getTableHeader().setReorderingAllowed(false);
		// table.getTableHeader().setBackground(Color.white);
		table.getTableHeader().setDefaultRenderer(new HeroHeaderRenderer());
		table.getColumnModel().getColumn(0).setHeaderValue(column1);
		table.getColumnModel().getColumn(1).setHeaderValue(column2);
		if (col3Present) {
			table.getColumnModel().getColumn(2).setHeaderValue(column3);
		}
		table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
		table.getColumnModel().getColumn(0).setResizable(false);
		table.getColumnModel().getColumn(1).setResizable(false);
		if (col3Present) {
			table.getColumnModel().getColumn(2).setResizable(false);
		}
		table.getColumnModel().getColumn(0).setMaxWidth(Constants.COL_1_WIDTH);
		table.getColumnModel().getColumn(0).setMinWidth(Constants.COL_1_WIDTH);
		if (col3Present) {
			table.getColumnModel().getColumn(2).setMaxWidth(
					Constants.COL_3_WIDTH);
			table.getColumnModel().getColumn(2).setMinWidth(
					Constants.COL_3_WIDTH);
		}

		table.setDefaultRenderer(String.class, new HeroRenderer());

		table.setDefaultEditor(String.class, new HeroEditor());
		// jHitList.setDefaultEditor(String.class, new
		// JVICellEditor(jHitList));

		insertBtn = new JButton("Insert At End");
		insertBefore = false;
		Image image = Toolkit.getDefaultToolkit().createImage(
				ClassLoader.getSystemResource("up.gif"));
		upBtn = new JButton(new ImageIcon(image));
		upBtn.setEnabled(false);
		upBtn.setMargin(new Insets(0, 0, 0, 0));
		Image image2 = Toolkit.getDefaultToolkit().createImage(
				ClassLoader.getSystemResource("down.gif"));
		downBtn = new JButton(new ImageIcon(image2));
		downBtn.setEnabled(false);
		downBtn.setMargin(new Insets(0, 0, 0, 0));
		editBtn = new JButton("Edit");
		editBtn.setEnabled(false);
		deleteBtn = new JButton("Delete");
		deleteBtn.setEnabled(false);
	}

	private void layoutComponent() {
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridwidth = 1;
		gbc.gridheight = 1;
		gbc.weightx = 1;
		gbc.weighty = 0;
		gbc.fill = GridBagConstraints.NONE;
		gbc.anchor = GridBagConstraints.NORTH;

		gbc.gridy = 1;
		gbc.weighty = 1;
		gbc.anchor = GridBagConstraints.NORTH;
		gbc.gridy = 2;
		gbc.weightx = 0;
		gbc.weighty = 0;
		gbc.fill = GridBagConstraints.NONE;
		gbc.anchor = GridBagConstraints.SOUTH;

		scroll = new JScrollPane(table);
		scroll.getViewport().setBackground(Color.white);
		scroll.getVerticalScrollBar().setUnitIncrement(10);
		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(scroll, BorderLayout.CENTER);
		getContentPane().add(getButtonPanel(), BorderLayout.SOUTH);
	}

	private void normalize() {
		for (int i = 0; i < objects.size(); i++) {
			GenericObject o = objects.get(i);
			o.setPosition(i);
		}
		syncSelectedIndex();
	}

	/**
	 * Removes the specified selection listener from the JTable.
	 * 
	 * @param listener
	 */
	public void removeSelectionListener(SelectionListener listener) {
		table.getSelectionModel().removeListSelectionListener(listener);
	}

	protected void setCopyAction(ActionListener listener) {
		if (copyListener != null) {
			copyMI.removeActionListener(copyListener);
		}
		copyMI.addActionListener(listener);
		copyListener = listener;
	}

	protected void setCutAction(ActionListener listener) {
		if (cutListener != null) {
			cutMI.removeActionListener(cutListener);
		}
		cutMI.addActionListener(listener);
		cutListener = listener;
	}

	/**
	 * Sets the data Vector that is being tracked.
	 * 
	 * @param data
	 */
	public void setData(ArrayList<GenericObject> data) {
		objects = data;
		updateTotal();
		syncSelectedIndex();
	}

	protected void setPasteAction(ActionListener listener) {
		if (pasteListener != null) {
			pasteMI.removeActionListener(pasteListener);
		}
		pasteMI.addActionListener(listener);
		pasteListener = listener;
	}

	/**
	 * Sets the currently selected index in the purchase list.
	 * 
	 * @param index
	 * @param edit
	 */
	public synchronized void setSelectedIndex(int index, boolean edit) {
		if (!locked) {
			table.requestFocus();
		}
		if ((index < 0) || (index >= objects.size())) {
			return;
		} else {
			selectedIndex = index;
		}
		if (selectedIndex < 0) {
			if (!locked) {
				this.requestFocus(true);
			}
		}
		if (selectedIndex >= 0) {
			if (table.getSelectedRow() != selectedIndex) {
				table.setRowSelectionInterval(selectedIndex, selectedIndex);
			}
		} else {
			table.clearSelection();
		}

		upBtn.setEnabled(checkUp());
		downBtn.setEnabled(checkDown());
		if ((selectedIndex >= 0) && (objects.get(selectedIndex) != null)) {
			GenericObject o = objects.get(selectedIndex);
			deleteBtn
					.setEnabled(!(objects.get(selectedIndex) instanceof Adder));
			if (!((o instanceof com.hero.objects.List) && (o.getTextOutput()
					.trim().length() == 0))) {
				editBtn
						.setEnabled(!(objects.get(selectedIndex) instanceof Adder));
				if (edit && !(objects.get(selectedIndex) instanceof Adder)) {
					editBtn.doClick();
				}
			}
		} else {
			editBtn.setEnabled(false);
			deleteBtn.setEnabled(false);
		}
		if ((selectedIndex >= 0) && insertBefore) {
			insertBtn.setText("Insert Before");
		} else if (insertBefore) {
			insertBtn.setText("Insert At Top");
		} else if (selectedIndex >= 0) {
			insertBtn.setText("Insert After");
		} else {
			insertBtn.setText("Insert At End");
		}

		if ((selectedIndex >= 0) && !locked) {
			Rectangle rect = table.getCellRect(selectedIndex, 1, true);
			rect.setRect(rect.getX(), rect.getY() + 30, rect.getWidth(), rect
					.getHeight());
			table.scrollRectToVisible(rect);
		}
		syncSelectedIndex();
	}

	/**
	 * Updates the total cost displayed in the top title bar.
	 */
	public void setTotalCost() {
		if (type.equals("Disadvantages") || type.equals("Complications")) {
			if (HeroDesigner.getActiveTemplate().is6E()) {
				type = "Complications";
				table.getColumnModel().getColumn(1).setHeaderValue(
						"Complication");
			} else {
				type = "Disadvantages";
				table.getColumnModel().getColumn(1).setHeaderValue(
						"Disadvantage");
			}
		}
		if ((totalCostCalcTime > 0)
				&& (totalCostCalcTime > HeroDesigner.lastEdit)) {
			return;
		}
		double cost = 0;
		double carried = 0;
		double weight = 0;
		long currentTime = System.currentTimeMillis();
		boolean equipment = type.equals("Equipment");
		for (int i = 0; i < objects.size(); i++) {
			GenericObject obj = objects.get(i);
			if (equipment) {
				cost += obj.getTotalPrice();
			} else {
				cost += obj.getRealCost();
			}
			if (obj.isCarried()) {
				carried += obj.getTotalWeight();
			}
			weight += obj.getTotalWeight();
			thisTotal = cost;
			thisCarried = carried;
			thisWeight = weight;
			lastSet = currentTime;
		}
		String totCost = "" + Rounder.roundUp(cost);
		BigDecimal bd = new BigDecimal(carried);
		bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
		String totCarried = bd.toString()
				+ (HeroDesigner.getInstance().getPrefs().isMetric() ? " kg"
						: " lbs");
		bd = new BigDecimal(weight);
		bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
		String totWeight = bd.toString()
				+ (HeroDesigner.getInstance().getPrefs().isMetric() ? " kg"
						: " lbs");
		if (equipment) {
			bd = new BigDecimal(cost);
			bd = bd.setScale(HeroDesigner.getActiveHero().getRules()
					.getEquipmentCostDecimalPlaces(), BigDecimal.ROUND_HALF_UP);
			totCost = bd.toString();
			if (HeroDesigner.getActiveHero().getRules()
					.isEquipmentUnitsPrefix()) {
				totCost = HeroDesigner.getActiveHero().getRules()
						.getEquipmentCostUnits()
						+ totCost;
			} else {
				totCost += HeroDesigner.getActiveHero().getRules()
						.getEquipmentCostUnits();
			}
		}
		double total = HeroDesigner.getActiveHero().getSpentTotal();
		double avail = HeroDesigner.getActiveHero().getBasePoints()
				+ HeroDesigner.getActiveHero().getDisadPoints()
				+ HeroDesigner.getActiveHero().getExperience();
		if (HeroDesigner.getActiveTemplate().is6E()) {
			avail -= HeroDesigner.getActiveHero().getDisadPoints();
		}
		if (equipment) {
			setTitle(type + " Totals -- Cost: " + totCost + ", Carried: "
					+ totCarried + ", Weight: " + totWeight);
		} else {
			setTitle(type
					+ " -- Total "
					+ type
					+ (equipment ? " Cost: " : " Points: ")
					+ totCost
					+ (equipment ? "" : " (Total Spent: "
							+ Rounder.roundUp(total)
							+ (HeroDesigner.getActiveHero().isPrefab() ? ""
									: "/" + Rounder.roundUp(avail)) + ")"));
		}
	}

	private void syncSelectedIndex() {
		int check = table.getSelectedRow();
		if (check != selectedIndex) {
			System.out.println("Selected index out of sync!  Resetting...");
			selectedIndex = check;
			setSelectedIndex(selectedIndex, false);
		}
	}

	/**
	 * Updates the entire object, resetting the JTable, and notifying all
	 * listeners of an update.
	 */
	public void updateTotal() {
		if (this instanceof SkillList) {
			((SkillList) this).setEnhancerLists();
		}
		if (this instanceof PerksList) {
			((PerksList) this).setEnhancerLists();
		}
		setTotalCost();
		int val = selectedIndex;
		model.fireTableDataChanged();
		if (val < 0) {
			val = -1;
		}
		if (val >= objects.size()) {
			val = objects.size() - 1;
		}
		locked = true;
		synchronized (table) {
			setSelectedIndex(val, false);
		}
		for (UpdateListener listener : listeners) {
			listener.updatePerformed();
		}
		syncSelectedIndex();
		locked = false;
	}
}
