/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.layers.plants;
import org.pepsoft.util.Version;
import org.pepsoft.worldpainter.biomeschemes.BiomeSchemeManager;
import org.pepsoft.worldpainter.layers.AbstractLayerEditor;
import org.pepsoft.worldpainter.layers.exporters.ExporterSettings;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.SortedMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static org.pepsoft.worldpainter.layers.plants.Plant.Category.*;
import static org.pepsoft.worldpainter.util.I18nHelper.m;
/**
*
* @author Pepijn Schmitz
*/
public class PlantLayerEditor extends AbstractLayerEditor<PlantLayer> {
/**
* Creates new form PlantLayerEditor
*/
public PlantLayerEditor() {
initComponents();
initPlantControls();
fieldName.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
settingsChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
settingsChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
settingsChanged();
}
});
setLabelColour();
setControlStates();
}
// LayerEditor
@Override
public PlantLayer createLayer() {
return new PlantLayer("Plants", "A custom collection of plants", Color.GREEN.getRGB());
}
@Override
public void setLayer(PlantLayer layer) {
super.setLayer(layer);
reset();
}
@Override
public void commit() {
if (! isCommitAvailable()) {
throw new IllegalStateException("Settings invalid or incomplete");
}
saveSettings(layer);
}
@Override
public void reset() {
fieldName.setText(layer.getName());
selectedColour = layer.getColour();
checkBoxGenerateTilledDirt.setSelected(layer.isGenerateTilledDirt());
for (int i = 0; i < Plant.ALL_PLANTS.length; i++) {
PlantLayer.PlantSettings settings = layer.getSettings(i);
if (settings != null) {
spinners[i].setValue((int) settings.occurrence);
if (growthFromSpinners[i] != null) {
growthFromSpinners[i].setValue(settings.dataValueFrom + 1);
growthToSpinners[i].setValue(settings.dataValueTo + 1);
}
} else {
spinners[i].setValue(0);
if (growthFromSpinners[i] != null) {
growthFromSpinners[i].setValue(Plant.ALL_PLANTS[i].getMaxData() + 1);
growthToSpinners[i].setValue(Plant.ALL_PLANTS[i].getMaxData() + 1);
}
}
}
updatePercentages();
}
@Override
public ExporterSettings getSettings() {
if (! isCommitAvailable()) {
throw new IllegalStateException("Settings invalid or incomplete");
}
final PlantLayer previewLayer = saveSettings(null);
return new ExporterSettings() {
@Override
public boolean isApplyEverywhere() {
return false;
}
@Override
public PlantLayer getLayer() {
return previewLayer;
}
@Override
public ExporterSettings clone() {
throw new UnsupportedOperationException("Not supported");
}
};
}
@Override
public boolean isCommitAvailable() {
return (! fieldName.getText().trim().isEmpty()) && (totalOccurrence > 0);
}
private void initPlantControls() {
panelPlantControls.setLayout(new GridBagLayout());
panelPlantControls2.setLayout(new GridBagLayout());
addCategory(panelPlantControls, PLANTS_AND_FLOWERS);
addCategory(panelPlantControls2, SAPLINGS);
addCategory(panelPlantControls2, CROPS);
addCategory(panelPlantControls2, "Various", MUSHROOMS, CACTUS, SUGAR_CANE, WATER_PLANTS, NETHER, END);
}
private void addCategory(JPanel panel, Plant.Category category) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.anchor = GridBagConstraints.BASELINE_LEADING;
constraints.insets = new Insets(4, 0, 4, 0);
panel.add(new JLabel("<html><b>" + m(category) + "</b></html>"), constraints);
for (int i = 0; i < Plant.ALL_PLANTS.length; i++) {
Plant plant = Plant.ALL_PLANTS[i];
if (plant.getCategory() == category) {
addPlantRow(panel, plant, i);
}
}
}
private void addCategory(JPanel panel, String title, Plant.Category... categories) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.anchor = GridBagConstraints.BASELINE_LEADING;
constraints.insets = new Insets(4, 0, 4, 0);
panel.add(new JLabel("<html><b>" + title + "</b></html>"), constraints);
for (Plant.Category category: categories) {
for (int i = 0; i < Plant.ALL_PLANTS.length; i++) {
Plant plant = Plant.ALL_PLANTS[i];
if (plant.getCategory() == category) {
addPlantRow(panel, plant, i);
}
}
}
}
private void addPlantRow(final JPanel panel, final Plant plant, final int index) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.anchor = GridBagConstraints.BASELINE_LEADING;
constraints.insets = new Insets(1, 0, 1, 4);
BufferedImage icon = findIcon(plant.getIconName());
if (icon != null) {
plantLabels[index] = new JLabel(plant.getName(), new ImageIcon(icon), JLabel.TRAILING);
} else {
plantLabels[index] = new JLabel(plant.getName());
}
panel.add(plantLabels[index], constraints);
SpinnerModel spinnerModel = new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1);
spinners[index] = new JSpinner(spinnerModel);
((JSpinner.NumberEditor) spinners[index].getEditor()).getTextField().setColumns(3);
spinners[index].addChangeListener(percentageListener);
panel.add(spinners[index], constraints);
percentageLabels[index] = new JLabel("100%");
panel.add(percentageLabels[index], constraints);
if (plant.getMaxData() > 0) {
panel.add(new JLabel("Growth:"), constraints);
spinnerModel = new SpinnerNumberModel(plant.getMaxData() + 1, 1, plant.getMaxData() + 1, 1);
growthFromSpinners[index] = new JSpinner(spinnerModel);
growthFromSpinners[index].addChangeListener(e -> {
int newValue = (Integer) growthFromSpinners[index].getValue();
if ((Integer) growthToSpinners[index].getValue() < newValue) {
growthToSpinners[index].setValue(newValue);
}
});
panel.add(growthFromSpinners[index], constraints);
panel.add(new JLabel("-"));
constraints.gridwidth = GridBagConstraints.REMAINDER;
spinnerModel = new SpinnerNumberModel(plant.getMaxData() + 1, 1, plant.getMaxData() + 1, 1);
growthToSpinners[index] = new JSpinner(spinnerModel);
growthToSpinners[index].addChangeListener(e -> {
int newValue = (Integer) growthToSpinners[index].getValue();
if ((Integer) growthFromSpinners[index].getValue() > newValue) {
growthFromSpinners[index].setValue(newValue);
}
});
panel.add(growthToSpinners[index], constraints);
} else {
constraints.gridwidth = GridBagConstraints.REMAINDER;
panel.add(new JLabel(), constraints);
}
}
private static BufferedImage findIcon(String name) {
if (resourcesJar == RESOURCES_NOT_AVAILABLE) {
return null;
}
try {
if (resourcesJar == null) {
SortedMap<Version, File> jars = BiomeSchemeManager.getAllMinecraftJars();
if (! jars.isEmpty()) {
resourcesJar = jars.get(jars.lastKey());
} else {
logger.warn("Could not find Minecraft jar for loading plant icons");
resourcesJar = RESOURCES_NOT_AVAILABLE;
return null;
}
}
try (JarFile jarFile = new JarFile(resourcesJar)) {
JarEntry entry = jarFile.getJarEntry("assets/minecraft/textures/" + name);
if (entry != null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading plant icon " + name + " from " + resourcesJar);
}
try (InputStream in = jarFile.getInputStream(entry)) {
return ImageIO.read(in);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Could not find plant icon " + name + " in Minecraft jar " + resourcesJar);
}
return null;
}
}
} catch (IOException e) {
logger.error("I/O error while trying to load plant icon " + name + "; continuing without icon", e);
resourcesJar = RESOURCES_NOT_AVAILABLE;
return null;
}
}
private void updatePercentages() {
totalOccurrence = 0;
for (JSpinner spinner: spinners) {
totalOccurrence += (Integer) spinner.getValue();
}
if (normalFont == null) {
normalFont = plantLabels[0].getFont().deriveFont(Font.PLAIN);
boldFont = normalFont.deriveFont(Font.BOLD);
}
cropsSelected = false;
for (int i = 0; i < spinners.length; i++) {
int value = (Integer) spinners[i].getValue();
if ((value == 0) && percentageLabels[i].isEnabled()) {
percentageLabels[i].setEnabled(false);
plantLabels[i].setFont(normalFont);
percentageLabels[i].setText(" %");
if ((growthFromSpinners[i] != null) && growthFromSpinners[i].isEnabled()) {
growthFromSpinners[i].setEnabled(false);
growthToSpinners[i].setEnabled(false);
}
} else if (value > 0) {
if (! percentageLabels[i].isEnabled()) {
percentageLabels[i].setEnabled(true);
plantLabels[i].setFont(boldFont);
}
if (Plant.ALL_PLANTS[i].getCategory() == CROPS) {
cropsSelected = true;
}
int percentage = value * 100 / totalOccurrence;
if (percentage < 10) {
percentageLabels[i].setText(" " + percentage + "%");
} else if (percentage < 100) {
percentageLabels[i].setText(" " + percentage + "%");
} else {
percentageLabels[i].setText(percentage + "%");
}
if ((growthFromSpinners[i] != null) && (! growthFromSpinners[i].isEnabled())) {
growthFromSpinners[i].setEnabled(true);
growthToSpinners[i].setEnabled(true);
}
}
}
settingsChanged();
}
private void setLabelColour() {
labelColour.setBackground(new Color(selectedColour));
}
private void settingsChanged() {
setControlStates();
context.settingsChanged();
}
private void setControlStates() {
checkBoxGenerateTilledDirt.setEnabled(cropsSelected);
}
private void pickColour() {
Color pick = JColorChooser.showDialog(this, "Select Colour", new Color(selectedColour));
if (pick != null) {
selectedColour = pick.getRGB();
setLabelColour();
}
}
private PlantLayer saveSettings(PlantLayer layer) {
if (layer == null) {
layer = createLayer();
}
layer.setName(fieldName.getText().trim());
layer.setColour(selectedColour);
layer.setGenerateTilledDirt(checkBoxGenerateTilledDirt.isSelected());
for (int i = 0; i < Plant.ALL_PLANTS.length; i++) {
PlantLayer.PlantSettings settings = new PlantLayer.PlantSettings();
settings.occurrence = (short) ((int) ((Integer) spinners[i].getValue()));
if (growthFromSpinners[i] != null) {
settings.dataValueFrom = (byte) ((Integer) growthFromSpinners[i].getValue() - 1);
settings.dataValueTo = (byte) ((Integer) growthToSpinners[i].getValue() - 1);
} else {
settings.dataValueFrom = 0;
settings.dataValueTo = 0;
}
layer.setSettings(i, settings);
}
return layer;
}
private void clear() {
for (int i = 0; i < Plant.ALL_PLANTS.length; i++) {
spinners[i].setValue(0);
if (growthFromSpinners[i] != null) {
growthFromSpinners[i].setValue(Plant.ALL_PLANTS[i].getMaxData() + 1);
growthToSpinners[i].setValue(Plant.ALL_PLANTS[i].getMaxData() + 1);
}
}
updatePercentages();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonColour = new javax.swing.JButton();
jLabel3 = new javax.swing.JLabel();
labelColour = new javax.swing.JLabel();
panelPlantControls2 = new javax.swing.JPanel();
buttonClear = new javax.swing.JButton();
jLabel1 = new javax.swing.JLabel();
panelPlantControls = new javax.swing.JPanel();
checkBoxGenerateTilledDirt = new javax.swing.JCheckBox();
fieldName = new javax.swing.JTextField();
jLabel2 = new javax.swing.JLabel();
buttonColour.setText("...");
buttonColour.addActionListener(this::buttonColourActionPerformed);
jLabel3.setText("<html>Note that plants will only be placed<br>where Minecraft allows it!</html>");
labelColour.setText(" ");
labelColour.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
labelColour.setOpaque(true);
javax.swing.GroupLayout panelPlantControls2Layout = new javax.swing.GroupLayout(panelPlantControls2);
panelPlantControls2.setLayout(panelPlantControls2Layout);
panelPlantControls2Layout.setHorizontalGroup(
panelPlantControls2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
panelPlantControls2Layout.setVerticalGroup(
panelPlantControls2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
buttonClear.setText("Clear");
buttonClear.addActionListener(this::buttonClearActionPerformed);
jLabel1.setText("Name:");
javax.swing.GroupLayout panelPlantControlsLayout = new javax.swing.GroupLayout(panelPlantControls);
panelPlantControls.setLayout(panelPlantControlsLayout);
panelPlantControlsLayout.setHorizontalGroup(
panelPlantControlsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 225, Short.MAX_VALUE)
);
panelPlantControlsLayout.setVerticalGroup(
panelPlantControlsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
checkBoxGenerateTilledDirt.setSelected(true);
checkBoxGenerateTilledDirt.setText("turn grass and dirt beneath crops to tilled dirt");
checkBoxGenerateTilledDirt.setEnabled(false);
fieldName.setColumns(20);
fieldName.setText("jTextField1");
jLabel2.setText("Colour:");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(checkBoxGenerateTilledDirt)
.addGroup(layout.createSequentialGroup()
.addComponent(panelPlantControls, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(panelPlantControls2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(buttonClear))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fieldName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(labelColour)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(buttonColour)))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(fieldName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel2)
.addComponent(labelColour)
.addComponent(buttonColour))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(layout.createSequentialGroup()
.addComponent(checkBoxGenerateTilledDirt)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(panelPlantControls2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(panelPlantControls, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(18, 18, 18)
.addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(buttonClear))
.addGap(0, 0, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void buttonColourActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonColourActionPerformed
pickColour();
}//GEN-LAST:event_buttonColourActionPerformed
private void buttonClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonClearActionPerformed
clear();
}//GEN-LAST:event_buttonClearActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton buttonClear;
private javax.swing.JButton buttonColour;
private javax.swing.JCheckBox checkBoxGenerateTilledDirt;
private javax.swing.JTextField fieldName;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel labelColour;
private javax.swing.JPanel panelPlantControls;
private javax.swing.JPanel panelPlantControls2;
// End of variables declaration//GEN-END:variables
private final JSpinner[] spinners = new JSpinner[Plant.ALL_PLANTS.length];
private final JLabel[] plantLabels = new JLabel[Plant.ALL_PLANTS.length], percentageLabels = new JLabel[Plant.ALL_PLANTS.length];
private final JSpinner[] growthFromSpinners = new JSpinner[Plant.ALL_PLANTS.length], growthToSpinners = new JSpinner[Plant.ALL_PLANTS.length];
private int selectedColour = Color.ORANGE.getRGB(), totalOccurrence;
private boolean cropsSelected;
private Font normalFont, boldFont;
private final ChangeListener percentageListener = e -> updatePercentages();
private static File resourcesJar;
private static final File RESOURCES_NOT_AVAILABLE = new File("~~~RESOURCES_NOT_AVAILABLE~~~");
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(PlantLayerEditor.class);
}