/* * Forge Mod Loader * Copyright (c) 2012-2014 cpw. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors (this class): * bspkrs - implementation */ package cpw.mods.fml.client.config; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiListExtended; import net.minecraft.client.gui.GuiTextField; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.resources.I18n; import net.minecraft.util.EnumChatFormatting; import org.lwjgl.input.Keyboard; import static cpw.mods.fml.client.config.GuiUtils.INVALID; import static cpw.mods.fml.client.config.GuiUtils.VALID; import cpw.mods.fml.client.config.GuiConfigEntries.ArrayEntry; import cpw.mods.fml.common.FMLLog; /** * This class implements the scrolling list functionality of the GuiEditList screen. It also provides all the default controls * for editing array-type properties. * * @author bspkrs */ @SuppressWarnings("rawtypes") public class GuiEditArrayEntries extends GuiListExtended { private GuiEditArray owningGui; public Minecraft mc; public IConfigElement configElement; public List<IArrayEntry> listEntries; public boolean isDefault; public boolean isChanged; public boolean canAddMoreEntries; public final int controlWidth; public final Object[] beforeValues; public Object[] currentValues; @SuppressWarnings("unchecked") public GuiEditArrayEntries(GuiEditArray parent, Minecraft mc, IConfigElement configElement, Object[] beforeValues, Object[] currentValues) { super(mc, parent.width, parent.height, parent.titleLine2 != null ? (parent.titleLine3 != null ? 43 : 33) : 23, parent.height - 32, 20); this.owningGui = parent; this.mc = mc; this.configElement = configElement; this.beforeValues = beforeValues; this.currentValues = currentValues; this.setShowSelectionBox(false); this.isChanged = !Arrays.deepEquals(beforeValues, currentValues); this.isDefault = Arrays.deepEquals(currentValues, configElement.getDefaults()); this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || currentValues.length < configElement.getMaxListLength()); listEntries = new ArrayList<IArrayEntry>(); controlWidth = (parent.width / 2) - (configElement.isListLengthFixed() ? 0 : 48); if (configElement.isList() && configElement.getArrayEntryClass() != null) { Class<? extends IArrayEntry> clazz = configElement.getArrayEntryClass(); for (Object value : currentValues) { try { listEntries.add(clazz.getConstructor(GuiEditArray.class, GuiEditArrayEntries.class, IConfigElement.class, Object.class) .newInstance(this.owningGui, this, configElement, value.toString())); } catch (Throwable e) { FMLLog.severe("There was a critical error instantiating the custom IGuiEditListEntry for property %s.", configElement.getName()); e.printStackTrace(); } } } else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.BOOLEAN)) for (Object value : currentValues) listEntries.add(new BooleanEntry(this.owningGui, this, configElement, Boolean.valueOf(value.toString()))); else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.INTEGER)) for (Object value : currentValues) listEntries.add(new IntegerEntry(this.owningGui, this, configElement, Integer.parseInt(value.toString()))); else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.DOUBLE)) for (Object value : currentValues) listEntries.add(new DoubleEntry(this.owningGui, this, configElement, Double.parseDouble(value.toString()))); else if (configElement.isList()) for (Object value : currentValues) listEntries.add(new StringEntry(this.owningGui, this, configElement, value.toString())); if (!configElement.isListLengthFixed()) listEntries.add(new BaseEntry(this.owningGui, this, configElement)); } @Override protected int getScrollBarX() { return width - (width / 4); } /** * Gets the width of the list */ /** * Gets the width of the list */ @Override public int getListWidth() { return owningGui.width; } /** * Gets the IGuiListEntry object for the given index */ @Override public IArrayEntry getListEntry(int index) { return listEntries.get(index); } @Override protected int getSize() { return listEntries.size(); } public void addNewEntry(int index) { if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN) listEntries.add(index, new BooleanEntry(this.owningGui, this, this.configElement, Boolean.valueOf(true))); else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER) listEntries.add(index, new IntegerEntry(this.owningGui, this, this.configElement, 0)); else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE) listEntries.add(index, new DoubleEntry(this.owningGui, this, this.configElement, 0.0D)); else if (configElement.isList()) listEntries.add(index, new StringEntry(this.owningGui, this, this.configElement, "")); this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength()); keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END); } public void removeEntry(int index) { this.listEntries.remove(index); this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength()); keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END); } public boolean isChanged() { return isChanged; } public boolean isDefault() { return isDefault; } public void recalculateState() { isDefault = true; isChanged = false; int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1; if (listLength != configElement.getDefaults().length) { isDefault = false; } if (listLength != beforeValues.length) { isChanged = true; } if (isDefault) for (int i = 0; i < listLength; i++) if (!configElement.getDefaults()[i].equals(listEntries.get(i).getValue())) isDefault = false; if (!isChanged) for (int i = 0; i < listLength; i++) if (!beforeValues[i].equals(listEntries.get(i).getValue())) isChanged = true; } protected void keyTyped(char eventChar, int eventKey) { for (IArrayEntry entry : this.listEntries) entry.keyTyped(eventChar, eventKey); recalculateState(); } protected void updateScreen() { for (IArrayEntry entry : this.listEntries) entry.updateCursorCounter(); } protected void mouseClicked(int x, int y, int mouseEvent) { for (IArrayEntry entry : this.listEntries) entry.mouseClicked(x, y, mouseEvent); } protected boolean isListSavable() { for (IArrayEntry entry : this.listEntries) if (!entry.isValueSavable()) return false; return true; } @SuppressWarnings("unchecked") protected void saveListChanges() { int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1; if (owningGui.slotIndex != -1 && owningGui.parentScreen != null && owningGui.parentScreen instanceof GuiConfig && ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex) instanceof ArrayEntry) { ArrayEntry entry = (ArrayEntry) ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex); Object[] ao = new Object[listLength]; for (int i = 0; i < listLength; i++) ao[i] = listEntries.get(i).getValue(); entry.setListFromChildScreen(ao); } else { if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN) { Boolean[] abol = new Boolean[listLength]; for (int i = 0; i < listLength; i++) abol[i] = Boolean.valueOf(listEntries.get(i).getValue().toString()); configElement.set(abol); } else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER) { Integer[] ai = new Integer[listLength]; for (int i = 0; i < listLength; i++) ai[i] = Integer.valueOf(listEntries.get(i).getValue().toString()); configElement.set(ai); } else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE) { Double[] ad = new Double[listLength]; for (int i = 0; i < listLength; i++) ad[i] = Double.valueOf(listEntries.get(i).getValue().toString()); configElement.set(ad); } else if (configElement.isList()) { String[] as = new String[listLength]; for (int i = 0; i < listLength; i++) as[i] = listEntries.get(i).getValue().toString(); configElement.set(as); } } } protected void drawScreenPost(int mouseX, int mouseY, float f) { for (IArrayEntry entry : this.listEntries) entry.drawToolTip(mouseX, mouseY); } /** * IGuiListEntry Inner Classes */ public static class DoubleEntry extends StringEntry { public DoubleEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Double value) { super(owningScreen, owningEntryList, configElement, value); this.isValidated = true; } @Override public void keyTyped(char eventChar, int eventKey) { if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) { String validChars = "0123456789"; String before = this.textFieldValue.getText(); if (validChars.contains(String.valueOf(eventChar)) || (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-') || (!before.contains(".") && eventChar == '.') || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey); if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-")) { try { double value = Double.parseDouble(textFieldValue.getText().trim()); if (value < Double.valueOf(configElement.getMinValue().toString()) || value > Double.valueOf(configElement.getMaxValue().toString())) this.isValidValue = false; else this.isValidValue = true; } catch (Throwable e) { this.isValidValue = false; } } else this.isValidValue = false; } } @Override public Double getValue() { try { return Double.valueOf(this.textFieldValue.getText().trim()); } catch (Throwable e) { return Double.MAX_VALUE; } } } public static class IntegerEntry extends StringEntry { public IntegerEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Integer value) { super(owningScreen, owningEntryList, configElement, value); this.isValidated = true; } @Override public void keyTyped(char eventChar, int eventKey) { if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) { String validChars = "0123456789"; String before = this.textFieldValue.getText(); if (validChars.contains(String.valueOf(eventChar)) || (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-') || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey); if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-")) { try { long value = Long.parseLong(textFieldValue.getText().trim()); if (value < Integer.valueOf(configElement.getMinValue().toString()) || value > Integer.valueOf(configElement.getMaxValue().toString())) this.isValidValue = false; else this.isValidValue = true; } catch (Throwable e) { this.isValidValue = false; } } else this.isValidValue = false; } } @Override public Integer getValue() { try { return Integer.valueOf(this.textFieldValue.getText().trim()); } catch (Throwable e) { return Integer.MAX_VALUE; } } } public static class StringEntry extends BaseEntry { protected final GuiTextField textFieldValue; public StringEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Object value) { super(owningScreen, owningEntryList, configElement); this.textFieldValue = new GuiTextField(owningEntryList.mc.fontRendererObj, owningEntryList.width / 4 + 1, 0, owningEntryList.controlWidth - 3, 16); this.textFieldValue.setMaxStringLength(10000); this.textFieldValue.setText(value.toString()); this.isValidated = configElement.getValidationPattern() != null; if (configElement.getValidationPattern() != null) { if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches()) isValidValue = true; else isValidValue = false; } } @Override public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) { super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); if (configElement.isListLengthFixed() || slotIndex != owningEntryList.listEntries.size() - 1) { this.textFieldValue.setVisible(true); this.textFieldValue.yPosition = y + 1; this.textFieldValue.drawTextBox(); } else this.textFieldValue.setVisible(false); } @Override public void keyTyped(char eventChar, int eventKey) { if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) { this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey); if (configElement.getValidationPattern() != null) { if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches()) isValidValue = true; else isValidValue = false; } } } @Override public void updateCursorCounter() { this.textFieldValue.updateCursorCounter(); } @Override public void mouseClicked(int x, int y, int mouseEvent) { this.textFieldValue.mouseClicked(x, y, mouseEvent); } @Override public Object getValue() { return this.textFieldValue.getText().trim(); } } public static class BooleanEntry extends BaseEntry { protected final GuiButtonExt btnValue; private boolean value; public BooleanEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, boolean value) { super(owningScreen, owningEntryList, configElement); this.value = value; this.btnValue = new GuiButtonExt(0, 0, 0, owningEntryList.controlWidth, 18, I18n.format(String.valueOf(value))); this.btnValue.enabled = owningScreen.enabled; this.isValidated = false; } @Override public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) { super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); this.btnValue.xPosition = listWidth / 4; this.btnValue.yPosition = y; String trans = I18n.format(String.valueOf(value)); if (!trans.equals(String.valueOf(value))) this.btnValue.displayString = trans; else this.btnValue.displayString = String.valueOf(value); btnValue.packedFGColour = value ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true); this.btnValue.drawButton(owningEntryList.mc, mouseX, mouseY); } /** * Returns true if the mouse has been pressed on this control. */ @Override public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) { if (this.btnValue.mousePressed(owningEntryList.mc, x, y)) { btnValue.playPressSound(owningEntryList.mc.getSoundHandler()); value = !value; owningEntryList.recalculateState(); return true; } return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY); } /** * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY */ @Override public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) { this.btnValue.mouseReleased(x, y); super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY); } @Override public Object getValue() { return Boolean.valueOf(value); } } public static class BaseEntry implements IArrayEntry { protected final GuiEditArray owningScreen; protected final GuiEditArrayEntries owningEntryList; protected final IConfigElement configElement; protected final GuiButtonExt btnAddNewEntryAbove; private final HoverChecker addNewEntryAboveHoverChecker; protected final GuiButtonExt btnRemoveEntry; private final HoverChecker removeEntryHoverChecker; private List addNewToolTip, removeToolTip; protected boolean isValidValue = true; protected boolean isValidated = false; @SuppressWarnings({ "unchecked" }) public BaseEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement) { this.owningScreen = owningScreen; this.owningEntryList = owningEntryList; this.configElement = configElement; this.btnAddNewEntryAbove = new GuiButtonExt(0, 0, 0, 18, 18, "+"); this.btnAddNewEntryAbove.packedFGColour = GuiUtils.getColorCode('2', true); this.btnAddNewEntryAbove.enabled = owningScreen.enabled; this.btnRemoveEntry = new GuiButtonExt(0, 0, 0, 18, 18, "x"); this.btnRemoveEntry.packedFGColour = GuiUtils.getColorCode('c', true); this.btnRemoveEntry.enabled = owningScreen.enabled; this.addNewEntryAboveHoverChecker = new HoverChecker(this.btnAddNewEntryAbove, 800); this.removeEntryHoverChecker = new HoverChecker(this.btnRemoveEntry, 800); this.addNewToolTip = new ArrayList(); this.removeToolTip = new ArrayList(); addNewToolTip.add(I18n.format("fml.configgui.tooltip.addNewEntryAbove")); removeToolTip.add(I18n.format("fml.configgui.tooltip.removeEntry")); } @Override public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) { if (this.getValue() != null && this.isValidated) owningEntryList.mc.fontRendererObj.drawString( isValidValue ? EnumChatFormatting.GREEN + VALID : EnumChatFormatting.RED + INVALID, listWidth / 4 - owningEntryList.mc.fontRendererObj.getStringWidth(VALID) - 2, y + slotHeight / 2 - owningEntryList.mc.fontRendererObj.FONT_HEIGHT / 2, 16777215); int half = listWidth / 2; if (owningEntryList.canAddMoreEntries) { this.btnAddNewEntryAbove.visible = true; this.btnAddNewEntryAbove.xPosition = half + ((half / 2) - 44); this.btnAddNewEntryAbove.yPosition = y; this.btnAddNewEntryAbove.drawButton(owningEntryList.mc, mouseX, mouseY); } else this.btnAddNewEntryAbove.visible = false; if (!configElement.isListLengthFixed() && slotIndex != owningEntryList.listEntries.size() - 1) { this.btnRemoveEntry.visible = true; this.btnRemoveEntry.xPosition = half + ((half / 2) - 22); this.btnRemoveEntry.yPosition = y; this.btnRemoveEntry.drawButton(owningEntryList.mc, mouseX, mouseY); } else this.btnRemoveEntry.visible = false; } @Override public void drawToolTip(int mouseX, int mouseY) { boolean canHover = mouseY < owningEntryList.bottom && mouseY > owningEntryList.top; if (this.btnAddNewEntryAbove.visible && this.addNewEntryAboveHoverChecker.checkHover(mouseX, mouseY, canHover)) owningScreen.drawToolTip(this.addNewToolTip, mouseX, mouseY); if (this.btnRemoveEntry.visible && this.removeEntryHoverChecker.checkHover(mouseX, mouseY, canHover)) owningScreen.drawToolTip(this.removeToolTip, mouseX, mouseY); } /** * Returns true if the mouse has been pressed on this control. */ @Override public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) { if (this.btnAddNewEntryAbove.mousePressed(owningEntryList.mc, x, y)) { btnAddNewEntryAbove.playPressSound(owningEntryList.mc.getSoundHandler()); owningEntryList.addNewEntry(index); owningEntryList.recalculateState(); return true; } else if (this.btnRemoveEntry.mousePressed(owningEntryList.mc, x, y)) { btnRemoveEntry.playPressSound(owningEntryList.mc.getSoundHandler()); owningEntryList.removeEntry(index); owningEntryList.recalculateState(); return true; } return false; } /** * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY */ @Override public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) { this.btnAddNewEntryAbove.mouseReleased(x, y); this.btnRemoveEntry.mouseReleased(x, y); } @Override public void keyTyped(char eventChar, int eventKey) {} @Override public void updateCursorCounter() {} @Override public void mouseClicked(int x, int y, int mouseEvent) {} @Override public boolean isValueSavable() { return isValidValue; } @Override public Object getValue() { return null; } } public static interface IArrayEntry extends GuiListExtended.IGuiListEntry { public void keyTyped(char eventChar, int eventKey); public void updateCursorCounter(); public void mouseClicked(int x, int y, int mouseEvent); public void drawToolTip(int mouseX, int mouseY); public boolean isValueSavable(); public Object getValue(); } }