/* * 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.equipment; import com.trollworks.gcs.feature.FeaturesPanel; import com.trollworks.gcs.prereq.PrereqsPanel; import com.trollworks.gcs.weapon.MeleeWeaponEditor; import com.trollworks.gcs.weapon.RangedWeaponEditor; import com.trollworks.gcs.weapon.WeaponStats; import com.trollworks.gcs.widgets.outline.RowEditor; import com.trollworks.toolkit.annotation.Localize; import com.trollworks.toolkit.ui.UIUtilities; 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 com.trollworks.toolkit.utility.units.WeightValue; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.util.ArrayList; 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; /** The detailed editor for {@link Equipment}s. */ public class EquipmentEditor extends RowEditor<Equipment> implements ActionListener, DocumentListener, FocusListener { @Localize("A reference to the book and page this equipment 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 diese Ausrüstung 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 equipo (p.e. B22 se refiere al \"Manual Básico\", página 22).") private static String REFERENCE_TOOLTIP; @Localize("The value of one of these pieces of equipment") @Localize(locale = "de", value = "Der Wert eines einzelnen Ausrüstungsgegenstandes") @Localize(locale = "ru", value = "Цена снаряжения") @Localize(locale = "es", value = "Valor unitario de cada pieza de equipo.") private static String VALUE_TOOLTIP; @Localize("The value of all of these pieces of equipment, plus the value of any contained equipment") @Localize(locale = "de", value = "Der Wert aller dieser Ausrüstungsgegenstände und der Wert der darin enthaltenen Ausrüstung") @Localize(locale = "ru", value = "Цена всего снаряжения, плюс цена любого входящего в него снаряжения") @Localize(locale = "es", value = "Valor de todas las piezas de equipo, más el valor de lo que contengan.") private static String EXT_VALUE_TOOLTIP; @Localize("Name") @Localize(locale = "de", value = "Name") @Localize(locale = "ru", value = "Название") @Localize(locale = "es", value = "Nombre") private static String NAME; @Localize("The name/description of the equipment, without any notes") @Localize(locale = "de", value = "Der Name des Ausrüstungsgegenstands, ohne Anmerkungen") @Localize(locale = "ru", value = "Название/описание снаряжения без заметок") @Localize(locale = "es", value = "Nombre/descripción del equipo, sin notas.") private static String NAME_TOOLTIP; @Localize("The name field may not be empty") @Localize(locale = "de", value = "Der Name 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("Tech Level") @Localize(locale = "de", value = "Techlevel") @Localize(locale = "ru", value = "Технологический уровень") @Localize(locale = "es", value = "Nivel Tecnológico") private static String EDITOR_TECH_LEVEL; @Localize("The first Tech Level this equipment is available at") @Localize(locale = "de", value = "Der Techlevel, ab dem diese Ausrüstung zur Verfügung steht") @Localize(locale = "ru", value = "Первый тех. уровень этого снаряжения доступен с") @Localize(locale = "es", value = "Nivel Tecnológico a partir del cual el equipo está disponible.") private static String EDITOR_TECH_LEVEL_TOOLTIP; @Localize("Legality Class") @Localize(locale = "de", value = "Legalitätsklasse") @Localize(locale = "ru", value = "Клас легальности") @Localize(locale = "es", value = "Nivel Legal") private static String EDITOR_LEGALITY_CLASS; @Localize("The legality class of this equipment") @Localize(locale = "de", value = "Die Legalitätsklasse des Ausrüstungsgegenstandes") @Localize(locale = "ru", value = "Класс легальности снаряжения") @Localize(locale = "es", value = "Nivel legal del equipo") private static String EDITOR_LEGALITY_CLASS_TOOLTIP; @Localize("Quantity") @Localize(locale = "de", value = "Anzahl") @Localize(locale = "ru", value = "Количество") @Localize(locale = "es", value = "Cantidad") private static String EDITOR_QUANTITY; @Localize("The number of this equipment present") @Localize(locale = "ru", value = "Количество этого снаряжения") @Localize(locale = "es", value = "Cantidad de equipo presente") private static String EDITOR_QUANTITY_TOOLTIP; @Localize("Value") @Localize(locale = "de", value = "Wert") @Localize(locale = "ru", value = "Цена") @Localize(locale = "es", value = "Valor") private static String EDITOR_VALUE; @Localize("Extended Value") @Localize(locale = "de", value = "Gesamtwert") @Localize(locale = "ru", value = "Полная цена") @Localize(locale = "es", value = "Valor ampliado") private static String EDITOR_EXTENDED_VALUE; @Localize("Weight") @Localize(locale = "de", value = "Gewicht") @Localize(locale = "ru", value = "Вес") @Localize(locale = "es", value = "Peso") private static String EDITOR_WEIGHT; @Localize("The weight of one of these pieces of equipment") @Localize(locale = "de", value = "Das Gewicht eines einzelnen Ausrüstungsgegenstandes") @Localize(locale = "ru", value = "Вес снаряжения") @Localize(locale = "es", value = "Peso unitario de una pieza de equipo") private static String EDITOR_WEIGHT_TOOLTIP; @Localize("Extended Weight") @Localize(locale = "de", value = "Gesamtgewicht") @Localize(locale = "ru", value = "Полный вес") @Localize(locale = "es", value = "Peso ampliado") private static String EDITOR_EXTENDED_WEIGHT; @Localize("The total weight of this quantity of equipment, plus everything contained by it") @Localize(locale = "de", value = "Das Gewicht aller dieser Ausrüstungsgegenstände und das Gewicht der darin enthaltenen Ausrüstung") @Localize(locale = "ru", value = "Общий вес имеющегося снаряжения и его содержимого") @Localize(locale = "es", value = "Peso total de todo el equipo, más el peso de lo que contengan") private static String EDITOR_EXTENDED_WEIGHT_TOOLTIP; @Localize("Categories") @Localize(locale = "de", value = "Kategorien") @Localize(locale = "ru", value = "Категории") @Localize(locale = "es", value = "Categorías") private static String CATEGORIES; @Localize("The category or categories the equipment belongs to (separate multiple categories with a comma)") @Localize(locale = "de", value = "Die Kategorie oder Kategorien, denen diese Ausrüstung angehört (trenne mehrere Kategorien mit einem Komma)") @Localize(locale = "ru", value = "Категория или категории снаряжения, к которым оно принадлежит (несколько категорий разделяются точкой с запятой)") @Localize(locale = "es", value = "Categoría o categorías a las que pertenece el equipo (múltiples categorías separadas con comas)") private static String CATEGORIES_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 equipment") @Localize(locale = "de", value = "Anmerkungen, die in der Liste neben der Ausrüstung erscheinen sollen") @Localize(locale = "ru", value = "Заметки, которые показываются в списке рядом с снаряжением") @Localize(locale = "es", value = "Cualquier nota que te gustaría mostar en la lista de equipo") private static String NOTES_TOOLTIP; @Localize("Page Reference") @Localize(locale = "de", value = "Seitenangabe") @Localize(locale = "ru", value = "Ссылка на страницу") @Localize(locale = "es", value = "Página de referencia") private static String EDITOR_REFERENCE; @Localize("Items that are not equipped do not apply any features they may normally contribute to the character.") @Localize(locale = "de", value = "Gegenstände, die nicht ausgerüstet sind, haben keine Auswirkungen auf den Charakter.") @Localize(locale = "ru", value = "Не экипированные предметы не добавляют свойств, которые обычно может использовать персонаж.") @Localize(locale = "es", value = "Los objetos no equipados no aplican sus características como harían normalmente al personaje.") private static String STATE_TOOLTIP; static { Localization.initialize(); } private JComboBox<EquipmentState> mStateCombo; private JTextField mDescriptionField; private JTextField mTechLevelField; private JTextField mLegalityClassField; private JTextField mQtyField; private JTextField mValueField; private JTextField mExtValueField; private JTextField mWeightField; private JTextField mExtWeightField; private JTextField mNotesField; private JTextField mCategoriesField; private JTextField mReferenceField; private JTabbedPane mTabPanel; private PrereqsPanel mPrereqs; private FeaturesPanel mFeatures; private MeleeWeaponEditor mMeleeWeapons; private RangedWeaponEditor mRangedWeapons; private double mContainedValue; private WeightValue mContainedWeight; /** * Creates a new {@link Equipment} editor. * * @param equipment The {@link Equipment} to edit. */ public EquipmentEditor(Equipment equipment) { super(equipment); JPanel content = new JPanel(new ColumnLayout(2)); JPanel fields = new JPanel(new ColumnLayout(2)); JLabel icon = new JLabel(equipment.getIcon(true)); JPanel wrapper = new JPanel(new ColumnLayout(2)); mDescriptionField = createCorrectableField(fields, NAME, equipment.getDescription(), NAME_TOOLTIP); createSecondLineFields(fields); createValueAndWeightFields(fields); mNotesField = createField(fields, fields, NOTES, equipment.getNotes(), NOTES_TOOLTIP, 0); mCategoriesField = createField(fields, fields, CATEGORIES, equipment.getCategoriesAsString(), CATEGORIES_TOOLTIP, 0); mReferenceField = createField(fields, wrapper, EDITOR_REFERENCE, mRow.getReference(), REFERENCE_TOOLTIP, 6); wrapper.add(new JPanel()); fields.add(wrapper); icon.setVerticalAlignment(SwingConstants.TOP); icon.setAlignmentY(-1f); content.add(icon); content.add(fields); add(content); mTabPanel = new JTabbedPane(); mPrereqs = new PrereqsPanel(mRow, mRow.getPrereqs()); mFeatures = new FeaturesPanel(mRow, mRow.getFeatures()); mMeleeWeapons = MeleeWeaponEditor.createEditor(mRow); mRangedWeapons = RangedWeaponEditor.createEditor(mRow); mTabPanel.addTab(mMeleeWeapons.getName(), mMeleeWeapons); mTabPanel.addTab(mRangedWeapons.getName(), mRangedWeapons); Component panel = embedEditor(mPrereqs); mTabPanel.addTab(panel.getName(), panel); panel = embedEditor(mFeatures); mTabPanel.addTab(panel.getName(), panel); if (!mIsEditable) { UIUtilities.disableControls(mMeleeWeapons); UIUtilities.disableControls(mRangedWeapons); } UIUtilities.selectTab(mTabPanel, getLastTabName()); add(mTabPanel); } private boolean showEquipmentState() { return mRow.getCharacter() != null; } private void createSecondLineFields(Container parent) { boolean isContainer = mRow.canHaveChildren(); JPanel wrapper = new JPanel(new ColumnLayout((isContainer ? 4 : 6) + (showEquipmentState() ? 1 : 0))); if (!isContainer) { mQtyField = createIntegerNumberField(parent, wrapper, EDITOR_QUANTITY, mRow.getQuantity(), EDITOR_QUANTITY_TOOLTIP, 9); } mTechLevelField = createField(isContainer ? parent : wrapper, wrapper, EDITOR_TECH_LEVEL, mRow.getTechLevel(), EDITOR_TECH_LEVEL_TOOLTIP, 3); mLegalityClassField = createField(wrapper, wrapper, EDITOR_LEGALITY_CLASS, mRow.getLegalityClass(), EDITOR_LEGALITY_CLASS_TOOLTIP, 3); if (showEquipmentState()) { mStateCombo = new JComboBox<>(EquipmentState.values()); mStateCombo.setSelectedItem(mRow.getState()); UIUtilities.setOnlySize(mStateCombo, mStateCombo.getPreferredSize()); mStateCombo.setEnabled(mIsEditable); mStateCombo.setToolTipText(Text.wrapPlainTextForToolTip(STATE_TOOLTIP)); wrapper.add(mStateCombo); } wrapper.add(new JPanel()); parent.add(wrapper); } private void createValueAndWeightFields(Container parent) { JPanel wrapper = new JPanel(new ColumnLayout(4)); Component first; mContainedValue = mRow.getExtendedValue() - mRow.getValue() * mRow.getQuantity(); mValueField = createNumberField(parent, wrapper, EDITOR_VALUE, mRow.getValue(), VALUE_TOOLTIP, 13); mExtValueField = createNumberField(wrapper, wrapper, EDITOR_EXTENDED_VALUE, mRow.getExtendedValue(), EXT_VALUE_TOOLTIP, 13); first = wrapper.getComponent(1); mExtValueField.setEnabled(false); wrapper.add(new JPanel()); parent.add(wrapper); wrapper = new JPanel(new ColumnLayout(3)); mContainedWeight = new WeightValue(mRow.getExtendedWeight()); WeightValue weight = new WeightValue(mRow.getWeight()); weight.setValue(weight.getValue() * mRow.getQuantity()); mContainedWeight.subtract(weight); mWeightField = createWeightField(parent, wrapper, EDITOR_WEIGHT, mRow.getWeight(), EDITOR_WEIGHT_TOOLTIP, 13); mExtWeightField = createWeightField(wrapper, wrapper, EDITOR_EXTENDED_WEIGHT, mRow.getExtendedWeight(), EDITOR_EXTENDED_WEIGHT_TOOLTIP, 13); mExtWeightField.setEnabled(false); UIUtilities.adjustToSameSize(new Component[] { first, wrapper.getComponent(1) }); parent.add(wrapper); } 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; } private JTextField createCorrectableField(Container parent, String title, String text, String tooltip) { JTextField field = new JTextField(text); field.setToolTipText(Text.wrapPlainTextForToolTip(tooltip)); field.setEnabled(mIsEditable); field.getDocument().addDocumentListener(this); field.addFocusListener(this); LinkedLabel label = new LinkedLabel(title); label.setLink(field); parent.add(label); parent.add(field); return field; } 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); field.addFocusListener(this); labelParent.add(new LinkedLabel(title, field)); fieldParent.add(field); return field; } @SuppressWarnings("unused") private JTextField createIntegerNumberField(Container labelParent, Container fieldParent, String title, int 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, false, false, true, maxDigits); field.addActionListener(this); field.addFocusListener(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, ',') + "."); //$NON-NLS-1$ 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); field.addFocusListener(this); labelParent.add(new LinkedLabel(title, field)); fieldParent.add(field); return field; } private JTextField createWeightField(Container labelParent, Container fieldParent, String title, WeightValue value, String tooltip, int maxDigits) { JTextField field = new JTextField(Text.makeFiller(maxDigits, '9') + Text.makeFiller(maxDigits / 3, ',') + "."); //$NON-NLS-1$ UIUtilities.setOnlySize(field, field.getPreferredSize()); field.setText(value.toString()); field.setToolTipText(Text.wrapPlainTextForToolTip(tooltip)); field.setEnabled(mIsEditable); field.addActionListener(this); field.addFocusListener(this); labelParent.add(new LinkedLabel(title, field)); fieldParent.add(field); return field; } @Override public boolean applyChangesSelf() { boolean modified = mRow.setDescription(mDescriptionField.getText()); modified |= mRow.setReference(mReferenceField.getText()); modified |= mRow.setTechLevel(mTechLevelField.getText()); modified |= mRow.setLegalityClass(mLegalityClassField.getText()); modified |= mRow.setQuantity(getQty()); modified |= mRow.setValue(Numbers.extractDouble(mValueField.getText(), 0.0, true)); modified |= mRow.setWeight(WeightValue.extract(mWeightField.getText(), true)); if (showEquipmentState()) { modified |= mRow.setState((EquipmentState) mStateCombo.getSelectedItem()); } modified |= mRow.setNotes(mNotesField.getText()); modified |= mRow.setCategories(mCategoriesField.getText()); if (mPrereqs != null) { modified |= mRow.setPrereqs(mPrereqs.getPrereqList()); } if (mFeatures != null) { modified |= mRow.setFeatures(mFeatures.getFeatures()); } if (mMeleeWeapons != null) { ArrayList<WeaponStats> list = new ArrayList<>(mMeleeWeapons.getWeapons()); list.addAll(mRangedWeapons.getWeapons()); modified |= mRow.setWeapons(list); } return modified; } @Override public void finished() { if (mTabPanel != null) { updateLastTabName(mTabPanel.getTitleAt(mTabPanel.getSelectedIndex())); } } @Override public void actionPerformed(ActionEvent event) { adjustForChange(event.getSource()); } private int getQty() { if (mQtyField != null) { return Numbers.extractInteger(mQtyField.getText(), 0, true); } return 1; } private void valueChanged() { int qty = getQty(); double value; if (qty < 1) { value = 0; } else { value = qty * Numbers.extractDouble(mValueField.getText(), 0.0, true) + mContainedValue; } mExtValueField.setText(Numbers.format(value)); } private void weightChanged() { WeightValue weight = WeightValue.extract(mWeightField.getText(), true); weight.setValue(weight.getValue() * Math.max(getQty(), 0)); mExtWeightField.setText(weight.toString()); } @Override public void changedUpdate(DocumentEvent event) { descriptionChanged(); } @Override public void insertUpdate(DocumentEvent event) { descriptionChanged(); } @Override public void removeUpdate(DocumentEvent event) { descriptionChanged(); } private void descriptionChanged() { LinkedLabel.setErrorMessage(mDescriptionField, mDescriptionField.getText().trim().length() != 0 ? null : NAME_CANNOT_BE_EMPTY); } @Override public void focusGained(FocusEvent event) { // Not used. } @Override public void focusLost(FocusEvent event) { adjustForChange(event.getSource()); } private void adjustForChange(Object field) { if (field == mValueField) { valueChanged(); } else if (field == mWeightField) { weightChanged(); } else if (field == mQtyField) { valueChanged(); weightChanged(); } } }