/*
* Copyright (c) 1998-2017 by Richard A. Wilkes. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, version 2.0.
*/
package com.trollworks.gcs.weapon;
import com.trollworks.gcs.skill.Defaults;
import com.trollworks.gcs.skill.SkillDefault;
import com.trollworks.gcs.widgets.outline.ListRow;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.ui.Selection;
import com.trollworks.toolkit.ui.UIUtilities;
import com.trollworks.toolkit.ui.border.EmptyBorder;
import com.trollworks.toolkit.ui.image.StdImage;
import com.trollworks.toolkit.ui.layout.ColumnLayout;
import com.trollworks.toolkit.ui.layout.PrecisionLayout;
import com.trollworks.toolkit.ui.layout.RowDistribution;
import com.trollworks.toolkit.ui.widget.Commitable;
import com.trollworks.toolkit.ui.widget.EditorField;
import com.trollworks.toolkit.ui.widget.IconButton;
import com.trollworks.toolkit.ui.widget.LinkedLabel;
import com.trollworks.toolkit.ui.widget.outline.Outline;
import com.trollworks.toolkit.ui.widget.outline.OutlineModel;
import com.trollworks.toolkit.ui.widget.outline.Row;
import com.trollworks.toolkit.utility.Localization;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;
/** An abstract editor for weapon statistics. */
public abstract class WeaponEditor extends JPanel implements ActionListener, PropertyChangeListener {
@Localize("Usage")
@Localize(locale = "de", value = "Nutzungsart")
@Localize(locale = "ru", value = "Применение")
@Localize(locale = "es", value = "Uso")
private static String USAGE;
@Localize("Damage")
@Localize(locale = "de", value = "Schaden")
@Localize(locale = "ru", value = "Повреждения")
@Localize(locale = "es", value = "Daño")
private static String DAMAGE;
@Localize("Minimum Strength")
@Localize(locale = "de", value = "Mindeststärke")
@Localize(locale = "ru", value = "Минимальная сила")
@Localize(locale = "es", value = "Mínimo en fuerza")
private static String MINIMUM_STRENGTH;
@Localize("Add an attack")
@Localize(locale = "de", value = "Angriff hinzufügen")
@Localize(locale = "ru", value = "Добавить атаку")
@Localize(locale = "es", value = "Añadir un ataque")
private static String ADD_TOOLTIP;
@Localize("Remove the selected attacks")
@Localize(locale = "de", value = "Ausgewählte Angriffe entfernen")
@Localize(locale = "ru", value = "Удалить выбранные атаки")
@Localize(locale = "es", value = "Eliminar los ataques seleccionados")
private static String REMOVE_TOOLTIP;
static {
Localization.initialize();
}
protected static final String EMPTY = ""; //$NON-NLS-1$
private ListRow mOwner;
private WeaponOutline mOutline;
private IconButton mAddButton;
private IconButton mDeleteButton;
private JPanel mEditorPanel;
private EditorField mUsage;
private EditorField mDamage;
private EditorField mStrength;
private Defaults mDefaults;
private WeaponStats mWeapon;
private Class<? extends WeaponStats> mWeaponClass;
private boolean mRespond;
/**
* Creates a new {@link WeaponEditor}.
*
* @param owner The owning row.
* @param weapons The weapons to modify.
* @param weaponClass The {@link Class} of weapons.
*/
public WeaponEditor(ListRow owner, List<WeaponStats> weapons, Class<? extends WeaponStats> weaponClass) {
super(new BorderLayout());
mOwner = owner;
mWeaponClass = weaponClass;
mAddButton = new IconButton(StdImage.ADD, ADD_TOOLTIP, () -> addWeapon());
mDeleteButton = new IconButton(StdImage.REMOVE, REMOVE_TOOLTIP, () -> mOutline.deleteSelection());
mDeleteButton.setEnabled(false);
Panel top = new Panel(new BorderLayout());
Panel left = new Panel(new PrecisionLayout());
left.add(mAddButton);
left.add(mDeleteButton);
top.add(left, BorderLayout.WEST);
top.add(createOutline(weapons, weaponClass), BorderLayout.CENTER);
add(top, BorderLayout.NORTH);
add(createEditorPanel(), BorderLayout.CENTER);
setName(toString());
}
/** @return The owner. */
public ListRow getOwner() {
return mOwner;
}
/** @return The weapon. */
protected WeaponStats getWeapon() {
return mWeapon;
}
/** @return The weapons in this editor. */
public List<WeaponStats> getWeapons() {
ArrayList<WeaponStats> weapons = new ArrayList<>();
for (Row row : mOutline.getModel().getRows()) {
weapons.add(((WeaponDisplayRow) row).getWeapon().clone(mOwner));
}
return weapons;
}
private Component createOutline(List<WeaponStats> weapons, Class<? extends WeaponStats> weaponClass) {
mOutline = new WeaponOutline();
OutlineModel model = mOutline.getModel();
WeaponColumn.addColumns(mOutline, weaponClass, true);
mOutline.setAllowColumnDrag(false);
mOutline.setAllowColumnResize(false);
mOutline.setAllowRowDrag(false);
for (WeaponStats weapon : weapons) {
if (mWeaponClass.isInstance(weapon)) {
model.addRow(new WeaponDisplayRow(weapon.clone(mOwner)));
}
}
mOutline.addActionListener(this);
Dimension size = mOutline.getMinimumSize();
if (size.height < 50) {
size.height = 50;
mOutline.setMinimumSize(size);
}
JScrollPane scroller = new JScrollPane(mOutline);
scroller.setColumnHeaderView(mOutline.getHeaderPanel());
return scroller;
}
private Container createEditorPanel() {
JPanel wrapper = new JPanel(new ColumnLayout(4));
wrapper.setBorder(new EmptyBorder(5));
mEditorPanel = new JPanel(new ColumnLayout(1, RowDistribution.GIVE_EXCESS_TO_LAST));
mEditorPanel.add(wrapper);
mUsage = createTextField(wrapper, USAGE, EMPTY);
mDamage = createTextField(wrapper, DAMAGE, EMPTY);
createFields(wrapper);
mStrength = createTextField(wrapper, MINIMUM_STRENGTH, EMPTY);
createDefaults(mEditorPanel);
setWeaponState(false);
return mEditorPanel;
}
/**
* Called to add the necessary fields.
*
* @param parent The parent to add the fields to.
*/
protected abstract void createFields(Container parent);
private void createDefaults(Container parent) {
mDefaults = new Defaults(new ArrayList<SkillDefault>());
mDefaults.removeAll();
mDefaults.addActionListener(this);
JScrollPane scrollPanel = new JScrollPane(mDefaults);
Dimension size = mDefaults.getMinimumSize();
if (size.height < 50) {
size.height = 50;
mDefaults.setMinimumSize(size);
}
scrollPanel.setPreferredSize(new Dimension(50, 50));
parent.add(scrollPanel);
}
/**
* Creates a new text field.
*
* @param parent The parent.
* @param title The title of the field.
* @param value The initial value.
* @return The newly created field.
*/
protected EditorField createTextField(Container parent, String title, Object value) {
DefaultFormatter formatter = new DefaultFormatter();
formatter.setOverwriteMode(false);
EditorField field = new EditorField(new DefaultFormatterFactory(formatter), this, SwingConstants.LEFT, value, null);
parent.add(new LinkedLabel(title, field));
parent.add(field);
return field;
}
@Override
public final void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (mOutline == source) {
handleOutline(event.getActionCommand());
} else if (mRespond) {
if (mDefaults == source) {
changeDefaults();
}
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (mRespond) {
Object source = event.getSource();
if (mUsage == source) {
changeUsage();
} else if (mDamage == source) {
changeDamage();
} else if (mStrength == source) {
changeStrength();
} else {
updateFromField(source);
}
}
}
/**
* Called to update from a field.
*
* @param field The field that was altered.
*/
protected abstract void updateFromField(Object field);
private void changeDefaults() {
mWeapon.setDefaults(mDefaults.getDefaults());
adjustOutlineToContent();
}
private void changeUsage() {
mWeapon.setUsage((String) mUsage.getValue());
adjustOutlineToContent();
}
private void changeDamage() {
mWeapon.setDamage((String) mDamage.getValue());
adjustOutlineToContent();
}
private void changeStrength() {
mWeapon.setStrength((String) mStrength.getValue());
adjustOutlineToContent();
}
/** Call to adjust the {@link Outline} to its new content. */
protected void adjustOutlineToContent() {
mOutline.sizeColumnsToFit();
mOutline.repaintSelection();
}
private void addWeapon() {
WeaponDisplayRow weapon = new WeaponDisplayRow(createWeaponStats());
OutlineModel model = mOutline.getModel();
model.addRow(weapon);
mOutline.sizeColumnsToFit();
model.select(weapon, false);
mOutline.revalidate();
mOutline.scrollSelectionIntoView();
mOutline.requestFocus();
}
/** @return A newly created {@link WeaponStats}. */
protected abstract WeaponStats createWeaponStats();
private void handleOutline(String cmd) {
if (Outline.CMD_SELECTION_CHANGED.equals(cmd)) {
OutlineModel model = mOutline.getModel();
Selection selection = model.getSelection();
int count = selection.getCount();
if (count == 1) {
setWeapon(((WeaponDisplayRow) model.getRowAtIndex(selection.firstSelectedIndex())).getWeapon());
} else {
setWeapon(null);
}
mDeleteButton.setEnabled(count > 0);
}
}
private void setWeapon(WeaponStats weapon) {
if (weapon != mWeapon) {
Commitable.sendCommitToFocusOwner();
mWeapon = weapon;
setWeaponState(mWeapon != null);
}
}
private void setWeaponState(boolean enabled) {
mRespond = false;
if (enabled) {
updateFields();
if (mAddButton.isEnabled()) {
mRespond = true;
} else {
UIUtilities.disableControls(mDefaults);
}
} else {
blankFields();
}
if (!mAddButton.isEnabled()) {
enabled = false;
}
enableFields(enabled);
}
/** Called to update the contents of the fields. */
protected void updateFields() {
mUsage.setValue(mWeapon.getUsage());
mDamage.setValue(mWeapon.getDamage());
mStrength.setValue(mWeapon.getStrength());
mDefaults.setDefaults(mWeapon.getDefaults());
}
/**
* Called to enable/disable all fields.
*
* @param enabled Whether to enable the fields.
*/
protected void enableFields(boolean enabled) {
mUsage.setEnabled(enabled);
mDamage.setEnabled(enabled);
mStrength.setEnabled(enabled);
}
/** Called to blank all fields. */
protected void blankFields() {
mUsage.setValue(EMPTY);
mDamage.setValue(EMPTY);
mStrength.setValue(EMPTY);
mDefaults.removeAll();
mDefaults.revalidate();
mDefaults.repaint();
}
}