/*
* 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.feature;
import com.trollworks.gcs.common.EditorPanel;
import com.trollworks.gcs.equipment.Equipment;
import com.trollworks.gcs.widgets.outline.ListRow;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.ui.UIUtilities;
import com.trollworks.toolkit.ui.image.StdImage;
import com.trollworks.toolkit.ui.layout.FlexGrid;
import com.trollworks.toolkit.ui.layout.FlexRow;
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.utility.Localization;
import com.trollworks.toolkit.utility.text.DoubleFormatter;
import com.trollworks.toolkit.utility.text.IntegerFormatter;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.SwingConstants;
import javax.swing.text.DefaultFormatterFactory;
/** A generic feature editor panel. */
public abstract class FeatureEditor extends EditorPanel {
@Localize("Add a feature")
@Localize(locale = "de", value = "Eine Eigenschaft hinzufügen")
@Localize(locale = "ru", value = "Добавить особенность")
@Localize(locale = "es", value = "Añade una característica")
private static String ADD_FEATURE_TOOLTIP;
@Localize("Remove this feature")
@Localize(locale = "de", value = "Diese Eigenschaft entfernen")
@Localize(locale = "ru", value = "Убрать эту особенность")
@Localize(locale = "es", value = "Eliminar esta característica")
private static String REMOVE_FEATURE_TOOLTIP;
@Localize("Gives an attribute bonus of")
@Localize(locale = "de", value = "Gibt einen Attributs-Bonus von")
@Localize(locale = "ru", value = "Даёт премию атрибуту")
@Localize(locale = "es", value = "Da una bonificación al atributo de")
private static String ATTRIBUTE_BONUS;
@Localize("Reduces the attribute cost of")
@Localize(locale = "de", value = "Reduziert die Attributskosten von")
@Localize(locale = "ru", value = "Снижает стоимость атрибута")
@Localize(locale = "es", value = "Reduce el coste del atributo en ")
private static String COST_REDUCTION;
@Localize("Reduces the contained weight by")
private static String CONTAINED_WEIGHT_REDUCTION;
@Localize("Gives a DR bonus of")
@Localize(locale = "de", value = "Gibt einen SR-Bonus von")
@Localize(locale = "ru", value = "Даёт премию СП")
@Localize(locale = "es", value = "Da una bonificación a RD de ")
private static String DR_BONUS;
@Localize("Gives a skill level bonus of")
@Localize(locale = "de", value = "Gibt einen Fertigkeitswert-Bonus von")
@Localize(locale = "ru", value = "Даёт премию к уровню умения")
@Localize(locale = "es", value = "Da una bonificación a la habilidad de")
private static String SKILL_BONUS;
@Localize("Gives a spell level bonus of")
@Localize(locale = "de", value = "Gibt für Zauber einen Fertigkeitswert-Bonus von")
@Localize(locale = "ru", value = "Даёт премию у уровню заклинания")
@Localize(locale = "es", value = "Da una bonificación al sortilegio de")
private static String SPELL_BONUS;
@Localize("Gives a weapon damage bonus of")
@Localize(locale = "de", value = "Gibt einen Waffen-Schaden-Bonus von")
@Localize(locale = "ru", value = "Даёт премию к урону от оружия")
@Localize(locale = "es", value = "Da una bonificación al daño de")
private static String WEAPON_BONUS;
@Localize("per level")
@Localize(locale = "de", value = "je Stufe")
@Localize(locale = "ru", value = "за уровень")
@Localize(locale = "es", value = "por nivel")
private static String PER_LEVEL;
@Localize("per die")
@Localize(locale = "de", value = "je Würfel")
@Localize(locale = "ru", value = "за кубик")
@Localize(locale = "es", value = "por dado")
private static String PER_DIE;
static {
Localization.initialize();
}
private static final String CHANGE_BASE_TYPE = "ChangeBaseType"; //$NON-NLS-1$
private static final String BLANK = " "; //$NON-NLS-1$
private static final Class<?>[] BASE_TYPES = new Class<?>[] { AttributeBonus.class, DRBonus.class, SkillBonus.class, SpellBonus.class, WeaponBonus.class, CostReduction.class, ContainedWeightReduction.class };
private static Class<?> LAST_ITEM_TYPE = SkillBonus.class;
private ListRow mRow;
private Feature mFeature;
private JComboBox<Object> mBaseTypeCombo;
private JComboBox<Object> mLeveledAmountCombo;
/**
* Creates a new {@link FeatureEditor}.
*
* @param row The row this feature will belong to.
* @param feature The feature to edit.
* @return The newly created editor panel.
*/
public static FeatureEditor create(ListRow row, Feature feature) {
if (feature instanceof AttributeBonus) {
return new AttributeBonusEditor(row, (AttributeBonus) feature);
}
if (feature instanceof DRBonus) {
return new DRBonusEditor(row, (DRBonus) feature);
}
if (feature instanceof SkillBonus) {
return new SkillBonusEditor(row, (SkillBonus) feature);
}
if (feature instanceof SpellBonus) {
return new SpellBonusEditor(row, (SpellBonus) feature);
}
if (feature instanceof WeaponBonus) {
return new WeaponBonusEditor(row, (WeaponBonus) feature);
}
if (feature instanceof CostReduction) {
return new CostReductionEditor(row, (CostReduction) feature);
}
if (feature instanceof ContainedWeightReduction) {
return new ContainedWeightReductionEditor(row, (ContainedWeightReduction) feature);
}
return null;
}
/**
* Creates a new {@link FeatureEditor}.
*
* @param row The row this feature will belong to.
* @param feature The feature to edit.
*/
public FeatureEditor(ListRow row, Feature feature) {
super();
mRow = row;
mFeature = feature;
rebuild();
}
/** Rebuilds the contents of this panel with the current feature settings. */
protected void rebuild() {
removeAll();
FlexGrid grid = new FlexGrid();
FlexRow right = new FlexRow();
rebuildSelf(grid, right);
if (mFeature != null) {
IconButton button = new IconButton(StdImage.REMOVE, REMOVE_FEATURE_TOOLTIP, () -> removeFeature());
add(button);
right.add(button);
}
IconButton button = new IconButton(StdImage.ADD, ADD_FEATURE_TOOLTIP, () -> addFeature());
add(button);
right.add(button);
grid.add(right, 0, 1);
grid.apply(this);
revalidate();
repaint();
}
/**
* Sub-classes must implement this method to add any components they want to be visible.
*
* @param grid The general {@link FlexGrid}. Add items in column 0.
* @param right The right-side {@link FlexRow}, situated in grid row 0, column 1.
*/
protected abstract void rebuildSelf(FlexGrid grid, FlexRow right);
/** @return The {@link JComboBox} that allows the base feature type to be changed. */
protected JComboBox<Object> addChangeBaseTypeCombo() {
List<String> choices = new ArrayList<>();
choices.add(ATTRIBUTE_BONUS);
choices.add(DR_BONUS);
choices.add(SKILL_BONUS);
choices.add(SPELL_BONUS);
choices.add(WEAPON_BONUS);
choices.add(COST_REDUCTION);
if (mRow instanceof Equipment) {
choices.add(CONTAINED_WEIGHT_REDUCTION);
}
Class<?> type = mFeature.getClass();
Object current = choices.get(0);
int length = choices.size();
for (int i = 0; i < length; i++) {
if (type.equals(BASE_TYPES[i])) {
current = choices.get(i);
break;
}
}
mBaseTypeCombo = addComboBox(CHANGE_BASE_TYPE, choices.toArray(), current);
return mBaseTypeCombo;
}
/**
* @param amt The current {@link LeveledAmount}.
* @param min The minimum value to allow.
* @param max The maximum value to allow.
* @return The {@link EditorField} that allows a {@link LeveledAmount} to be changed.
*/
protected EditorField addLeveledAmountField(LeveledAmount amt, int min, int max) {
AbstractFormatter formatter;
Object value;
Object prototype;
if (amt.isIntegerOnly()) {
formatter = new IntegerFormatter(min, max, true);
value = new Integer(amt.getIntegerAmount());
prototype = new Integer(max);
} else {
formatter = new DoubleFormatter(min, max, true);
value = new Double(amt.getAmount());
prototype = new Double(max + 0.25);
}
EditorField field = new EditorField(new DefaultFormatterFactory(formatter), this, SwingConstants.LEFT, value, prototype, null);
field.putClientProperty(LeveledAmount.class, amt);
UIUtilities.setOnlySize(field, field.getPreferredSize());
add(field);
return field;
}
/**
* @param amt The current leveled amount object.
* @param usePerDie Whether to use the "per die" message or the "per level" message.
* @return The {@link JComboBox} that allows a {@link LeveledAmount} to be changed.
*/
protected JComboBox<Object> addLeveledAmountCombo(LeveledAmount amt, boolean usePerDie) {
String per = usePerDie ? PER_DIE : PER_LEVEL;
mLeveledAmountCombo = addComboBox(LeveledAmount.ATTRIBUTE_PER_LEVEL, new Object[] { BLANK, per }, amt.isPerLevel() ? per : BLANK);
mLeveledAmountCombo.putClientProperty(LeveledAmount.class, amt);
return mLeveledAmountCombo;
}
/** @return The underlying feature. */
public Feature getFeature() {
return mFeature;
}
private void addFeature() {
JComponent parent = (JComponent) getParent();
try {
parent.add(create(mRow, (Feature) LAST_ITEM_TYPE.newInstance()));
} catch (Exception exception) {
// Shouldn't have a failure...
exception.printStackTrace(System.err);
}
if (mFeature == null) {
parent.remove(this);
}
parent.revalidate();
}
private void removeFeature() {
JComponent parent = (JComponent) getParent();
parent.remove(this);
if (parent.getComponentCount() == 0) {
parent.add(new NoFeature(mRow));
}
parent.revalidate();
parent.repaint();
}
@Override
public void actionPerformed(ActionEvent event) {
String command = event.getActionCommand();
JComponent parent = (JComponent) getParent();
if (LeveledAmount.ATTRIBUTE_PER_LEVEL.equals(command)) {
((Bonus) mFeature).getAmount().setPerLevel(mLeveledAmountCombo.getSelectedIndex() == 1);
} else if (CHANGE_BASE_TYPE.equals(command)) {
LAST_ITEM_TYPE = BASE_TYPES[mBaseTypeCombo.getSelectedIndex()];
if (!mFeature.getClass().equals(LAST_ITEM_TYPE)) {
Commitable.sendCommitToFocusOwner();
try {
parent.add(create(mRow, (Feature) LAST_ITEM_TYPE.newInstance()), UIUtilities.getIndexOf(parent, this));
} catch (Exception exception) {
// Shouldn't have a failure...
exception.printStackTrace(System.err);
}
parent.remove(this);
parent.revalidate();
parent.repaint();
}
} else {
super.actionPerformed(event);
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if ("value".equals(event.getPropertyName())) { //$NON-NLS-1$
EditorField field = (EditorField) event.getSource();
LeveledAmount amt = (LeveledAmount) field.getClientProperty(LeveledAmount.class);
if (amt != null) {
if (amt.isIntegerOnly()) {
amt.setAmount(((Integer) field.getValue()).intValue());
} else {
amt.setAmount(((Double) field.getValue()).doubleValue());
}
notifyActionListeners();
} else {
super.propertyChange(event);
}
} else {
super.propertyChange(event);
}
}
}