/*
* 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.modifier;
import com.trollworks.gcs.feature.FeaturesPanel;
import com.trollworks.gcs.widgets.outline.RowEditor;
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.ColumnLayout;
import com.trollworks.toolkit.ui.widget.LinkedLabel;
import com.trollworks.toolkit.utility.Localization;
import com.trollworks.toolkit.utility.text.NumberFilter;
import com.trollworks.toolkit.utility.text.Numbers;
import com.trollworks.toolkit.utility.text.Text;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.MessageFormat;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/** Editor for {@link Modifier}s. */
public class ModifierEditor extends RowEditor<Modifier> implements ActionListener, DocumentListener {
@Localize("Name")
@Localize(locale = "de", value = "Name")
@Localize(locale = "ru", value = "Название")
@Localize(locale = "es", value = "Nombre")
private static String NAME;
@Localize("Name of Modifier")
@Localize(locale = "de", value = "Name des Modifikators.")
@Localize(locale = "ru", value = "Название модификатора")
@Localize(locale = "es", value = "Nombre del Modificador")
private static String NAME_TOOLTIP;
@Localize("Notes")
@Localize(locale = "de", value = "Anmerkungen")
@Localize(locale = "ru", value = "Заметка")
@Localize(locale = "es", value = "Notas")
private static String NOTES;
@Localize("Any notes that you would like to show up in the list along with this modifier")
@Localize(locale = "de", value = "Anmerkungen, die in der Liste neben dem Modifikator erscheinen sollen.")
@Localize(locale = "ru", value = "Заметки, которые показываются в списке рядом с модификатором")
@Localize(locale = "es", value = "Cualquier nota que te gustaría mostar con este modificador")
private static String NOTES_TOOLTIP;
@Localize("The name field may not be empty")
@Localize(locale = "de", value = "Das Namensfeld darf nicht leer sein.")
@Localize(locale = "ru", value = "Поле \"Название\" не может быть пустым")
@Localize(locale = "es", value = "El nombre no puede estar en blanco")
private static String NAME_CANNOT_BE_EMPTY;
@Localize("Cost")
@Localize(locale = "de", value = "Kosten")
@Localize(locale = "ru", value = "Стоимость")
@Localize(locale = "es", value = "Coste")
private static String COST;
@Localize("The base cost modifier")
@Localize(locale = "de", value = "Die Grundkosten.")
@Localize(locale = "ru", value = "Базовая стоимость модификатора")
@Localize(locale = "es", value = "Coste básico del modificador")
private static String COST_TOOLTIP;
@Localize("Levels")
@Localize(locale = "de", value = "Stufen")
@Localize(locale = "ru", value = "Уровни")
@Localize(locale = "es", value = "Niveles")
private static String LEVELS;
@Localize("The number of levels this modifier has")
@Localize(locale = "de", value = "Die Anzahl der Stufen, die dieser Modifkiator hat.")
@Localize(locale = "ru", value = "Число уровней, которое имеет этот модификатор")
@Localize(locale = "es", value = "Número de niveles que este modificador tiene")
private static String LEVELS_TOOLTIP;
@Localize("Total")
@Localize(locale = "de", value = "Gesamt")
@Localize(locale = "ru", value = "Всего")
@Localize(locale = "es", value = "Total")
private static String TOTAL_COST;
@Localize("The cost modifier's total value")
@Localize(locale = "de", value = "Die Gesamtkosten des Modifikators.")
@Localize(locale = "ru", value = "Общая стоимость модификатора")
@Localize(locale = "es", value = "Valor del coste total del modificador")
private static String TOTAL_COST_TOOLTIP;
@Localize("{0} Per Level")
@Localize(locale = "de", value = "{0} pro Stufe")
@Localize(locale = "ru", value = "{0} за уровень")
@Localize(locale = "es", value = "{0} por Nivel")
private static String HAS_LEVELS;
@Localize("Enabled")
@Localize(locale = "de", value = "Aktiv")
@Localize(locale = "ru", value = "Включено")
@Localize(locale = "es", value = "Activo")
private static String ENABLED;
@Localize("Whether this modifier has been enabled or not")
@Localize(locale = "de", value = "Ob dieser Modifikator aktiv ist oder nicht.")
@Localize(locale = "ru", value = "Включить этот модификатор")
@Localize(locale = "es", value = "Determina si el modificador está activo o no")
private static String ENABLED_TOOLTIP;
@Localize("Ref")
@Localize(locale = "de", value = "Ref.")
@Localize(locale = "ru", value = "Ссыл")
private static String REFERENCE;
@Localize("A reference to the book and page this modifier appears on (e.g. B22 would refer to \"Basic Set\", page 22)")
@Localize(locale = "de", value = "Eine Referenz auf das Buch und die Seite, auf der dieser Modifikator beschrieben wird (z.B. B22 würde auf \"Basic Set\" Seite 22 verweisen).")
@Localize(locale = "ru", value = "Ссылка на страницу и книгу, описывающая модификатор (например B22 - \"Базовые правила\", страница 22)")
@Localize(locale = "es", value = "Referencia al libro y página donde se menciona el modificador (p.e. B22 se refiere al \"Manual Básico\", página 22).")
private static String REFERENCE_TOOLTIP;
static {
Localization.initialize();
}
private static final String EMPTY = ""; //$NON-NLS-1$
private JTextField mNameField;
private JCheckBox mEnabledField;
private JTextField mNotesField;
private JTextField mReferenceField;
private JTextField mCostField;
private JTextField mLevelField;
private JTextField mCostModifierField;
private FeaturesPanel mFeatures;
private JTabbedPane mTabPanel;
private JComboBox<Object> mCostType;
private JComboBox<Object> mAffects;
private int mLastLevel;
/**
* Creates a new {@link ModifierEditor}.
*
* @param modifier The {@link Modifier} to edit.
*/
public ModifierEditor(Modifier modifier) {
super(modifier);
JPanel content = new JPanel(new ColumnLayout(2));
JPanel fields = new JPanel(new ColumnLayout(2));
JLabel icon;
StdImage image = modifier.getIcon(true);
if (image != null) {
icon = new JLabel(image);
} else {
icon = new JLabel();
}
JPanel wrapper = new JPanel(new ColumnLayout(2));
mNameField = createCorrectableField(fields, wrapper, NAME, modifier.getName(), NAME_TOOLTIP);
mEnabledField = new JCheckBox(ENABLED, modifier.isEnabled());
mEnabledField.setToolTipText(Text.wrapPlainTextForToolTip(ENABLED_TOOLTIP));
mEnabledField.setEnabled(mIsEditable);
wrapper.add(mEnabledField);
fields.add(wrapper);
createCostModifierFields(fields);
wrapper = new JPanel(new ColumnLayout(3));
mNotesField = createField(fields, wrapper, NOTES, modifier.getNotes(), NOTES_TOOLTIP, 0);
mReferenceField = createField(wrapper, wrapper, REFERENCE, mRow.getReference(), REFERENCE_TOOLTIP, 6);
fields.add(wrapper);
icon.setVerticalAlignment(SwingConstants.TOP);
icon.setAlignmentY(-1f);
content.add(icon);
content.add(fields);
add(content);
mTabPanel = new JTabbedPane();
mFeatures = new FeaturesPanel(mRow, mRow.getFeatures());
Component panel = embedEditor(mFeatures);
mTabPanel.addTab(panel.getName(), panel);
UIUtilities.selectTab(mTabPanel, getLastTabName());
add(mTabPanel);
}
@Override
protected boolean applyChangesSelf() {
boolean modified = mRow.setName(mNameField.getText());
modified |= mRow.setReference(mReferenceField.getText());
modified |= mRow.setNotes(mNotesField.getText());
if (getCostType() == CostType.MULTIPLIER) {
modified |= mRow.setCostMultiplier(getCostMultiplier());
} else {
modified |= mRow.setCost(getCost());
}
if (hasLevels()) {
modified |= mRow.setLevels(getLevels());
modified |= mRow.setCostType(CostType.PERCENTAGE);
} else {
modified |= mRow.setLevels(0);
modified |= mRow.setCostType(getCostType());
}
modified |= mRow.setAffects((Affects) mAffects.getSelectedItem());
modified |= mRow.setEnabled(mEnabledField.isSelected());
if (mFeatures != null) {
modified |= mRow.setFeatures(mFeatures.getFeatures());
}
return modified;
}
private boolean hasLevels() {
return mCostType.getSelectedIndex() == 0;
}
@Override
public void finished() {
if (mTabPanel != null) {
updateLastTabName(mTabPanel.getTitleAt(mTabPanel.getSelectedIndex()));
}
}
private JTextField createCorrectableField(Container labelParent, Container fieldParent, String title, String text, String tooltip) {
JTextField field = new JTextField(text);
field.setToolTipText(Text.wrapPlainTextForToolTip(tooltip));
field.setEnabled(mIsEditable);
field.getDocument().addDocumentListener(this);
LinkedLabel label = new LinkedLabel(title);
label.setLink(field);
labelParent.add(label);
fieldParent.add(field);
return field;
}
@Override
public void actionPerformed(ActionEvent event) {
Object src = event.getSource();
if (src == mCostType) {
costTypeChanged();
} else if (src == mCostField || src == mLevelField) {
updateCostModifier();
}
}
private JTextField createField(Container labelParent, Container fieldParent, String title, String text, String tooltip, int maxChars) {
JTextField field = new JTextField(maxChars > 0 ? Text.makeFiller(maxChars, 'M') : text);
if (maxChars > 0) {
UIUtilities.setOnlySize(field, field.getPreferredSize());
field.setText(text);
}
field.setToolTipText(Text.wrapPlainTextForToolTip(tooltip));
field.setEnabled(mIsEditable);
labelParent.add(new LinkedLabel(title, field));
fieldParent.add(field);
return field;
}
private JScrollPane embedEditor(Container editor) {
JScrollPane scrollPanel = new JScrollPane(editor);
scrollPanel.setMinimumSize(new Dimension(500, 120));
scrollPanel.setName(editor.toString());
if (!mIsEditable) {
UIUtilities.disableControls(editor);
}
return scrollPanel;
}
@SuppressWarnings("unused")
private JTextField createNumberField(Container labelParent, Container fieldParent, String title, boolean allowSign, int value, String tooltip, int maxDigits) {
JTextField field = new JTextField(Text.makeFiller(maxDigits, '9') + Text.makeFiller(maxDigits / 3, ',') + (allowSign ? "-" : EMPTY)); //$NON-NLS-1$
UIUtilities.setOnlySize(field, field.getPreferredSize());
field.setText(Numbers.format(value));
field.setToolTipText(Text.wrapPlainTextForToolTip(tooltip));
field.setEnabled(mIsEditable);
new NumberFilter(field, false, allowSign, true, maxDigits);
field.addActionListener(this);
labelParent.add(new LinkedLabel(title, field));
fieldParent.add(field);
return field;
}
@SuppressWarnings("unused")
private JTextField createNumberField(Container labelParent, Container fieldParent, String title, double value, String tooltip, int maxDigits) {
JTextField field = new JTextField(Text.makeFiller(maxDigits, '9') + Text.makeFiller(maxDigits / 3, ',') + '.');
UIUtilities.setOnlySize(field, field.getPreferredSize());
field.setText(Numbers.format(value));
field.setToolTipText(Text.wrapPlainTextForToolTip(tooltip));
field.setEnabled(mIsEditable);
new NumberFilter(field, true, false, true, maxDigits);
field.addActionListener(this);
labelParent.add(new LinkedLabel(title, field));
fieldParent.add(field);
return field;
}
private void createCostModifierFields(Container parent) {
JPanel wrapper = new JPanel(new ColumnLayout(7));
mLastLevel = mRow.getLevels();
if (mLastLevel < 1) {
mLastLevel = 1;
}
if (mRow.getCostType() == CostType.MULTIPLIER) {
mCostField = createNumberField(parent, wrapper, COST, mRow.getCostMultiplier(), COST_TOOLTIP, 5);
} else {
mCostField = createNumberField(parent, wrapper, COST, true, mRow.getCost(), COST_TOOLTIP, 5);
}
createCostType(wrapper);
mLevelField = createNumberField(wrapper, wrapper, LEVELS, false, mLastLevel, LEVELS_TOOLTIP, 3);
mCostModifierField = createNumberField(wrapper, wrapper, TOTAL_COST, true, 0, TOTAL_COST_TOOLTIP, 9);
mAffects = createComboBox(wrapper, Affects.values(), mRow.getAffects());
mCostModifierField.setEnabled(false);
if (!mRow.hasLevels()) {
mLevelField.setText(EMPTY);
mLevelField.setEnabled(false);
}
parent.add(wrapper);
}
private JComboBox<Object> createComboBox(Container parent, Object[] items, Object selection) {
JComboBox<Object> combo = new JComboBox<>(items);
combo.setSelectedItem(selection);
combo.addActionListener(this);
combo.setMaximumRowCount(items.length);
UIUtilities.setOnlySize(combo, combo.getPreferredSize());
parent.add(combo);
return combo;
}
private void createCostType(Container parent) {
CostType[] types = CostType.values();
Object[] values = new Object[types.length + 1];
values[0] = MessageFormat.format(HAS_LEVELS, CostType.PERCENTAGE.toString());
System.arraycopy(types, 0, values, 1, types.length);
mCostType = createComboBox(parent, values, mRow.hasLevels() ? values[0] : mRow.getCostType());
}
private void costTypeChanged() {
boolean hasLevels = hasLevels();
if (hasLevels) {
mLevelField.setText(Numbers.format(mLastLevel));
} else {
mLastLevel = Numbers.extractInteger(mLevelField.getText(), 0, true);
mLevelField.setText(EMPTY);
}
mLevelField.setEnabled(hasLevels);
updateCostField();
updateCostModifier();
}
@SuppressWarnings("unused")
private void updateCostField() {
if (getCostType() == CostType.MULTIPLIER) {
new NumberFilter(mCostField, true, false, true, 5);
mCostField.setText(Numbers.format(Math.abs(Numbers.extractDouble(mCostField.getText(), 0, true))));
} else {
new NumberFilter(mCostField, false, true, true, 5);
mCostField.setText(Numbers.formatWithForcedSign(Numbers.extractInteger(mCostField.getText(), 0, true)));
}
}
private void updateCostModifier() {
boolean enabled = true;
if (hasLevels()) {
mCostModifierField.setText(Numbers.formatWithForcedSign(getCost() * getLevels()) + "%"); //$NON-NLS-1$
} else {
CostType costType = getCostType();
switch (costType) {
case PERCENTAGE:
default:
mCostModifierField.setText(Numbers.formatWithForcedSign(getCost()) + costType);
break;
case POINTS:
mCostModifierField.setText(Numbers.formatWithForcedSign(getCost()));
break;
case MULTIPLIER:
mCostModifierField.setText(costType + Numbers.format(getCostMultiplier()));
mAffects.setSelectedItem(Affects.TOTAL);
enabled = false;
break;
}
}
mAffects.setEnabled(mIsEditable && enabled);
}
private CostType getCostType() {
Object obj = mCostType.getSelectedItem();
if (!(obj instanceof CostType)) {
obj = CostType.PERCENTAGE;
}
return (CostType) obj;
}
private int getCost() {
return Numbers.extractInteger(mCostField.getText(), 0, true);
}
private double getCostMultiplier() {
return Numbers.extractDouble(mCostField.getText(), 0, true);
}
private int getLevels() {
return Numbers.extractInteger(mLevelField.getText(), 0, true);
}
@Override
public void changedUpdate(DocumentEvent event) {
nameChanged();
}
@Override
public void insertUpdate(DocumentEvent event) {
nameChanged();
}
@Override
public void removeUpdate(DocumentEvent event) {
nameChanged();
}
private void nameChanged() {
LinkedLabel.setErrorMessage(mNameField, mNameField.getText().trim().length() != 0 ? null : NAME_CANNOT_BE_EMPTY);
}
}