/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.macro.api.functions.input;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import org.apache.commons.lang3.StringUtils;
import com.t3.MD5Key;
import com.t3.client.TabletopTool;
import com.t3.language.I18N;
import com.t3.macro.MacroException;
import com.t3.macro.api.views.TokenView;
import com.t3.util.ImageManager;
public class InputFunctions {
/**
* <pre>
* <span style="font-family:sans-serif;">The input() function prompts the user to input several variable values at once.
*
* Each of the string parameters has the following format:
* "varname|value|prompt|inputType|options"
*
* Only the first section is required.
* varname - the variable name to be assigned
* value - sets the initial contents of the input field
* prompt - UI text shown for the variable
* inputType - specifies the type of input field
* options - a string of the form "opt1=val1; opt2=val2; ..."
*
* The inputType field can be any of the following (defaults to TEXT):
* TEXT - A text field.
* "value" sets the initial contents.
* The return value is the string in the text field.
* Option: WIDTH=nnn sets the width of the text field (default 16).
* LIST - An uneditable combo box.
* "value" populates the list, and has the form "item1,item2,item3..." (trailing empty strings are dropped)
* The return value is the numeric index of the selected item.
* Option: SELECT=nnn sets the initial selection (default 0).
* Option: VALUE=STRING returns the string contents of the selected item (default NUMBER).
* Option: TEXT=FALSE suppresses the text of the list item (default TRUE).
* Option: ICON=TRUE causes icon asset URLs to be extracted from the "value" and displayed (default FALSE).
* Option: ICONSIZE=nnn sets the size of the icons (default 50).
* CHECK - A checkbox.
* "value" sets the initial state of the box (anything but "" or "0" checks the box)
* The return value is 0 or 1.
* No options.
* RADIO - A group of radio buttons.
* "value" is a list "name1, name2, name3, ..." which sets the labels of the buttons.
* The return value is the index of the selected item.
* Option: SELECT=nnn sets the initial selection (default 0).
* Option: ORIENT=H causes the radio buttons to be laid out on one line (default V).
* Option: VALUE=STRING causes the return value to be the string of the selected item (default NUMBER).
* LABEL - A label.
* The "varname" is ignored and no value is assigned to it.
* Option: TEXT=FALSE, ICON=TRUE, ICONSIZE=nnn, as in the LIST type.
* PROPS - A sub-panel with multiple text boxes.
* "value" contains a StrProp of the form "key1=val1; key2=val2; ..."
* One text box is created for each key, populated with the matching value.
* Option: SETVARS=SUFFIXED causes variable assignment to each key name, with appended "_" (default NONE).
* Option: SETVARS=UNSUFFIXED causes variable assignment to each key name.
* TAB - A tabbed dialog tab is created. Subsequent variables are contained in the tab.
* Option: SELECT=TRUE causes this tab to be shown at start (default SELECT=FALSE).
*
* All inputTypes except TAB accept the option SPAN=TRUE, which causes the prompt to be hidden and the input
* control to span both columns of the dialog layout (default FALSE).
* </span>
* </pre>
* @param parameters a list of strings containing information as described above
* @return a HashMap with the returned values or null if the user clicked on cancel
* @author knizia.fan
* @throws MacroException
*/
public static Map<String, String> input(TokenView token, String... parameters) throws MacroException {
// Extract the list of specifier strings from the parameters
// "name | value | prompt | inputType | options"
List<String> varStrings = new ArrayList<String>();
for (Object param : parameters) {
String paramStr = (String) param;
if (StringUtils.isEmpty(paramStr)) {
continue;
}
// Multiple vars can be packed into a string, separated by "##"
for (String varString : StringUtils.splitByWholeSeparator(paramStr, "##")) {
if (StringUtils.isEmpty(paramStr)) {
continue;
}
varStrings.add(varString);
}
}
// Create VarSpec objects from each variable's specifier string
List<VarSpec> varSpecs = new ArrayList<VarSpec>();
for (String specifier : varStrings) {
VarSpec vs;
try {
vs = new VarSpec(specifier);
} catch (VarSpec.SpecifierException se) {
throw new MacroException(se);
} catch (InputType.OptionException oe) {
throw new MacroException(I18N.getText("macro.function.input.invalidOptionType", oe.key, oe.value, oe.type, specifier));
}
varSpecs.add(vs);
}
// Check if any variables were defined
if (varSpecs.isEmpty())
return Collections.emptyMap(); // No work to do, so treat it as a successful invocation.
// UI step 1 - First, see if a token is given
String dialogTitle = "Input Values";
if (token != null) {
String name = token.getName(), gm_name = token.getGMName();
boolean isGM = TabletopTool.getPlayer().isGM();
String extra = "";
if (isGM && gm_name != null && gm_name.compareTo("") != 0)
extra = " for " + gm_name;
else if (name != null && name.compareTo("") != 0)
extra = " for " + name;
dialogTitle = dialogTitle + extra;
}
// UI step 2 - build the panel with the input fields
InputPanel ip = new InputPanel(varSpecs);
// Calculate the height
// TODO: remove this workaround
int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
int maxHeight = screenHeight * 3 / 4;
Dimension ipPreferredDim = ip.getPreferredSize();
if (maxHeight < ipPreferredDim.height) {
ip.modifyMaxHeightBy(maxHeight - ipPreferredDim.height);
}
// UI step 3 - show the dialog
JOptionPane jop = new JOptionPane(ip, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
JDialog dlg = jop.createDialog(TabletopTool.getFrame(), dialogTitle);
// Set up callbacks needed for desired runtime behavior
dlg.addComponentListener(new FixupComponentAdapter(ip));
dlg.setVisible(true);
int dlgResult = JOptionPane.CLOSED_OPTION;
try {
dlgResult = (Integer) jop.getValue();
} catch (NullPointerException npe) {
}
dlg.dispose();
if (dlgResult == JOptionPane.CANCEL_OPTION || dlgResult == JOptionPane.CLOSED_OPTION)
return null;
HashMap<String, String> results=new HashMap<String, String>();
// Finally, assign values from the dialog box to the variables
for (ColumnPanel cp : ip.columnPanels) {
List<VarSpec> panelVars = cp.varSpecs;
List<JComponent> panelControls = cp.inputFields;
int numPanelVars = panelVars.size();
StringBuilder allAssignments = new StringBuilder(); // holds all values assigned in this tab
for (int varCount = 0; varCount < numPanelVars; varCount++) {
VarSpec vs = panelVars.get(varCount);
JComponent comp = panelControls.get(varCount);
String newValue = null;
switch (vs.inputType) {
case TEXT: {
newValue = ((JTextField) comp).getText();
break;
}
case LIST: {
Integer index = ((JComboBox) comp).getSelectedIndex();
if (vs.optionValues.optionEquals("VALUE", "STRING")) {
newValue = vs.valueList.get(index);
} else { // default is "NUMBER"
newValue = index.toString();
}
break;
}
case CHECK: {
Integer value = ((JCheckBox) comp).isSelected() ? 1 : 0;
newValue = value.toString();
break;
}
case RADIO: {
// This code assumes that the Box container returns components
// in the same order that they were added.
Component[] comps = ((Box) comp).getComponents();
int componentCount = 0;
Integer index = 0;
for (Component c : comps) {
if (c instanceof JRadioButton) {
JRadioButton radio = (JRadioButton) c;
if (radio.isSelected())
index = componentCount;
}
componentCount++;
}
if (vs.optionValues.optionEquals("VALUE", "STRING")) {
newValue = vs.valueList.get(index);
} else { // default is "NUMBER"
newValue = index.toString();
}
break;
}
case LABEL: {
newValue = null;
// The variable name is ignored and not set.
break;
}
case PROPS: {
// Read out and assign all the subvariables.
// The overall return value is a property string (as in StrPropFunctions.java) with all the new settings.
Component[] comps = ((JPanel) comp).getComponents();
StringBuilder sb = new StringBuilder();
int setVars = 0; // "NONE", no assignments made
if (vs.optionValues.optionEquals("SETVARS", "SUFFIXED"))
setVars = 1;
if (vs.optionValues.optionEquals("SETVARS", "UNSUFFIXED"))
setVars = 2;
if (vs.optionValues.optionEquals("SETVARS", "TRUE"))
setVars = 2; // for backward compatibility
for (int compCount = 0; compCount < comps.length; compCount += 2) {
String key = ((JLabel) comps[compCount]).getText().split("\\:")[0]; // strip trailing colon
String value = ((JTextField) comps[compCount + 1]).getText();
sb.append(key);
sb.append("=");
sb.append(value);
sb.append(" ; ");
switch (setVars) {
case 0:
// Do nothing
break;
case 1:
results.put(key + "_", value);
break;
case 2:
results.put(key, value);
break;
}
}
newValue = sb.toString();
break;
}
default:
// should never happen
newValue = null;
break;
}
// Set the variable to the value we got from the dialog box.
if (newValue != null) {
results.put(vs.name, newValue.trim());
allAssignments.append(vs.name + "=" + newValue.trim() + " ## ");
}
}
if (cp.tabVarSpec != null) {
results.put(cp.tabVarSpec.name, allAssignments.toString());
}
}
return results; // success
// for debugging:
//return debugOutput(varSpecs);
}
/**
* Gets icon from the asset manager. Code copied and modified from
* EditTokestaticnDialog.java
*/
static ImageIcon getIcon(String id, int size, ImageObserver io) {
// Extract the MD5Key from the URL
if (id == null)
return null;
MD5Key assetID = new MD5Key(id);
// Get the base image && find the new size for the icon
BufferedImage assetImage = ImageManager.getImage(assetID, io);
// Resize
if (assetImage.getWidth() > size || assetImage.getHeight() > size) {
Dimension dim = new Dimension(assetImage.getWidth(), assetImage.getWidth());
if (dim.height < dim.width) {
dim.height = (int) ((dim.height / (double) dim.width) * size);
dim.width = size;
} else {
dim.width = (int) ((dim.width / (double) dim.height) * size);
dim.height = size;
}
BufferedImage image = new BufferedImage(dim.width, dim.height, Transparency.BITMASK);
Graphics2D g = image.createGraphics();
g.drawImage(assetImage, 0, 0, dim.width, dim.height, null);
assetImage = image;
}
return new ImageIcon(assetImage);
}
}
//A sample for the TAB control
/*
*
* [h: status = input( "tab0 | Abilities | Enter abilities here | TAB",
* "blah|Max value is 18|Note|LABEL ## abils|Str=8;Con=8;Dex=8;Int=8;Wis=8;Cha=8|Abilities|PROPS"
* , "txt0|text on tab 0|",
* "tab1 | Options | Various options | TAB |select=true",
* "txt|default text ## ck|1|Toggle me|CHECK ## list|a,b,This is a very long item,d|Pick one|LIST ## foo|foo"
* )] [h: abort(status)] tab0 is set to [tab0] <br>tab1 is set to [tab1] <br>
* <br>list is set to [list] <br>get list from the tab1 variable:
* [getStrProp(tab1,"list","","##")]
*/
//A tall tab control, to demonstrate scrolling
/*
* [h: props =
* "a=3;b=bob;c=cow;d=40;e=55;f=33;g=big time;h=hello;i=interesting;j=jack;k=kow;l=leap;m=moon;"
* ] [h: status = input( "tab0 | Settings | Settings tooltip | TAB",
* "foo|||CHECK ## bar|bar",
* "tab1 | Options | Options tooltip | TAB | select=true",
* "num|a,b,c,d|Pick one|list ## zot|zot ## zam|zam",
* "tab2 | Options2 | Options2 tooltip | TAB | ", "p | " + props +
* " | Sample props | PROPS ## p2 | " + props + " | More props | PROPS ## p3 | "
* + props + " | Even more props | PROPS ## p4 | " + props +
* " | Still more props | PROPS", "num2|a,b,c,d|Pick one|list ",
* "num3|after all it's only a listbox here now isn't it dear?,b,c,d|Pick one|list|span=true ## ee|ee ## ff|ff ## gg|gg ## hh|hh ## ii|ii ## jj|jj ## kk|kk"
* , "tab3 | Empty | nothin' | TAB" )] [h: abort(status)] tab0 is [tab0]<br> foo
* is [foo]<br> tab1 is [tab1]<br> num is [num]<br> tab2 is [tab2]<br> tab3 is
* [tab3]<br>
*/
//Here's a sample input to exercise the options
/*
*
* Original props = [props =
* "Name=Longsword +1; Damage=1d8+1; Crit=1d6; Keyword=fire;"] [H: input( "foo",
* "YourName|George Washington|Your name|TEXT",
* "Weapon|Axe,Sword,Mace|Choose weapon|LIST",
* "WarCry|Attack!,No surrender!,I give up!|Pick a war cry|LIST|VALUE=STRING select=1"
* , "CA || Combat advantage| CHECK|",
* "props |"+props+"|Weapon properties|PROPS|setvars=true",
* "UsePower |1|Use the power|CHECK",
* "Weight|light,medium,heavy||RADIO|ORIENT=H select=1",
* "Ambition|Survive today, Defeat my enemies, Rule the world, Become immortal||RADIO|VALUE=STRING"
* ,
* "bar | a, b, c, d, e, f, g , h ,i j, k |Radio button test | RADIO | select=5 value = string ; oRiEnT =h;;;;"
* )]<br> <i>New values of variables:</i> <br>foo is [foo] <br>YourName is
* [YourName] <br>Weapon is [Weapon] <br>WarCry is [WarCry] <br>CA is [CA]
* <br>props is [props] <br>UsePower is [UsePower] <br>Weight is [Weight]
* <br>Ambition is [Ambition] <br> <br>Name is [Name], Damage is [Damage], Crit
* is [Crit], Keyword is [Keyword]
*/
//Here's a longer version of that sample, but the 9/14/08 checked in version of TabletopTool gets
//a stack overflow when this is pasted into chat (due to its length?)
/*
*
* Original props = [props =
* "Name=Longsword +1; Damage=1d8+1; Crit=1d6; Keyword=fire;"] [h: setPropVars =
* 1] [H: input( "foo", "YourName|George Washington|Your name|TEXT",
* "Weapon|Axe,Sword,Mace|Choose weapon|LIST",
* "WarCry|Attack!,No surrender!,I give up!|Pick a war cry|LIST|VALUE=STRING select=1"
* , "CA || Combat advantage| CHECK|",
* "props |"+props+"|Weapon properties|PROPS|setvars=true",
* "UsePower |1|Use the power|CHECK",
* "Weight|light,medium,heavy||RADIO|ORIENT=H select=1",
* "Ambition|Survive today, Defeat my enemies, Rule the world, Become immortal||RADIO|VALUE=STRING"
* ,
* "bar | a, b, c, d, e, f, g , h ,i j, k |Radio button test | RADIO | select=5 value = string ; oRiEnT =h;;;;"
* )]<br> <i>New values of variables:</i> <table border=0><tr
* style='font-weight:bold;'><td>Name </td><td>Value</td></tr>
* <tr><td>foo </td><td>{foo}</td></tr>
* <tr><td>YourName </td><td>{YourName}</td></tr>
* <tr><td>Weapon </td><td>{Weapon}</td></tr>
* <tr><td>WarCry </td><td>{WarCry}</td></tr>
* <tr><td>CA </td><td>{CA} </td></tr>
* <tr><td>props </td><td>{props} </td></tr>
* <tr
* ><td>UsePower </td><td>{UsePower} </td></
* tr>
* <tr><td>Weight </td><td>{Weight} </td></tr>
* <
* tr><td>Ambition </td><td>{Ambition} </td></
* tr> <tr><td> </td><td> </td></tr>
* <tr><td> </td><td> </td></tr> {if
* (setPropVars,
* "<tr><td>Name </td><td>"+Name+" </td></tr>
* <
* tr><td>Damage </td><td>"+Damage+" </td></tr
* > <tr><td>Crit </td><td>"+Crit+" </td></tr>
* <
* tr><td>Keyword </td><td>"+Keyword+" </td></
* tr>", "")} </td></tr> </table> New props = [props]
*/