diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java index 3b14bc9da9..466bed401d 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java @@ -99,11 +99,9 @@ class ConfigInvocationHandler implements InvocationHandler } // Convert value to return type - Class returnType = method.getReturnType(); - try { - Object objectValue = ConfigManager.stringToObject(value, returnType); + Object objectValue = manager.stringToObject(value, method.getGenericReturnType()); cache.put(method, objectValue == null ? NULL : objectValue); return objectValue; } @@ -155,7 +153,7 @@ class ConfigInvocationHandler implements InvocationHandler } else { - String newValueStr = ConfigManager.objectToString(newValue); + String newValueStr = manager.objectToString(newValue); manager.setConfiguration(group.value(), item.keyName(), newValueStr); } return null; diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java index d6ced58e18..922fb531a8 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java @@ -24,13 +24,14 @@ */ package net.runelite.client.config; +import java.lang.reflect.Type; import lombok.Value; @Value public class ConfigItemDescriptor implements ConfigObject { private final ConfigItem item; - private final Class type; + private final Type type; private final Range range; private final Alpha alpha; private final Units units; diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 6fbdb1011d..d6d4e384b0 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -30,6 +30,7 @@ import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableSet; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; +import com.google.gson.Gson; import java.awt.Color; import java.awt.Dimension; import java.awt.Point; @@ -44,6 +45,7 @@ import java.io.OutputStreamWriter; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.nio.channels.FileChannel; @@ -129,6 +131,7 @@ public class ConfigManager private final File settingsFileInput; private final EventBus eventBus; private final OkHttpClient okHttpClient; + private final Gson gson; private AccountSession session; private ConfigClient configClient; @@ -153,13 +156,15 @@ public class ConfigManager ScheduledExecutorService scheduledExecutorService, EventBus eventBus, OkHttpClient okHttpClient, - @Nullable Client client) + @Nullable Client client, + Gson gson) { this.settingsFileInput = config; this.eventBus = eventBus; this.okHttpClient = okHttpClient; this.client = client; this.propertiesFile = getPropertiesFile(); + this.gson = gson; scheduledExecutorService.scheduleWithFixedDelay(this::sendConfig, 30, 30, TimeUnit.SECONDS); } @@ -413,17 +418,12 @@ public class ConfigManager return properties.getProperty(getWholeKey(groupName, profile, key)); } - public T getConfiguration(String groupName, String key, Class clazz) + public T getConfiguration(String groupName, String key, Type clazz) { return getConfiguration(groupName, null, key, clazz); } - public Object getConfiguration(String groupName, String key, Type clazz) - { - return getConfiguration(groupName, null, key, clazz); - } - - public T getRSProfileConfiguration(String groupName, String key, Class clazz) + public T getRSProfileConfiguration(String groupName, String key, Type clazz) { String rsProfileKey = this.rsProfileKey; if (rsProfileKey == null) @@ -434,31 +434,14 @@ public class ConfigManager return getConfiguration(groupName, rsProfileKey, key, clazz); } - public T getConfiguration(String groupName, String profile, String key, Type clazz) + public T getConfiguration(String groupName, String profile, String key, Type type) { String value = getConfiguration(groupName, profile, key); if (!Strings.isNullOrEmpty(value)) { try { - return (T) stringToObject(value, (Class) clazz); - } - catch (Exception e) - { - log.warn("Unable to unmarshal {} ", getWholeKey(groupName, profile, key), e); - } - } - return null; - } - - public T getConfiguration(String groupName, String profile, String key, Class clazz) - { - String value = getConfiguration(groupName, profile, key); - if (!Strings.isNullOrEmpty(value)) - { - try - { - return (T) stringToObject(value, clazz); + return (T) stringToObject(value, type); } catch (Exception e) { @@ -511,12 +494,12 @@ public class ConfigManager eventBus.post(configChanged); } - public void setConfiguration(String groupName, String profile, String key, Object value) + public void setConfiguration(String groupName, String profile, String key, T value) { setConfiguration(groupName, profile, key, objectToString(value)); } - public void setConfiguration(String groupName, String key, Object value) + public void setConfiguration(String groupName, String key, T value) { // do not save consumers for buttons, they cannot be changed anyway if (value instanceof Consumer) @@ -527,7 +510,7 @@ public class ConfigManager setConfiguration(groupName, null, key, value); } - public void setRSProfileConfiguration(String groupName, String key, Object value) + public void setRSProfileConfiguration(String groupName, String key, T value) { String rsProfileKey = this.rsProfileKey; if (rsProfileKey == null) @@ -646,7 +629,7 @@ public class ConfigManager .collect(Collectors.toList()); final List titles = getAllDeclaredInterfaceFields(inter).stream() - .filter(m -> m.isAnnotationPresent(ConfigTitle.class) && m.getType() == String.class) + .filter(m -> m.isAnnotationPresent(ConfigTitle.class) && m.getClass().equals(String.class)) .map(m -> { try @@ -673,7 +656,7 @@ public class ConfigManager .filter(m -> m.getParameterCount() == 0 && m.isAnnotationPresent(ConfigItem.class)) .map(m -> new ConfigItemDescriptor( m.getDeclaredAnnotation(ConfigItem.class), - m.getReturnType(), + m.getGenericReturnType(), m.getDeclaredAnnotation(Range.class), m.getDeclaredAnnotation(Alpha.class), m.getDeclaredAnnotation(Units.class) @@ -748,7 +731,7 @@ public class ConfigManager { // This checks if it is set and is also unmarshallable to the correct type; so // we will overwrite invalid config values with the default - Object current = getConfiguration(group.value(), item.keyName(), method.getReturnType()); + Object current = getConfiguration(group.value(), item.keyName(), method.getGenericReturnType()); if (current != null) { continue; // something else is already set @@ -783,7 +766,7 @@ public class ConfigManager } } - static Object stringToObject(String str, Class type) + Object stringToObject(String str, Type type) { if (type == boolean.class || type == Boolean.class) { @@ -824,7 +807,7 @@ public class ConfigManager int height = Integer.parseInt(splitStr[3]); return new Rectangle(x, y, width, height); } - if (type.isEnum()) + if (type instanceof Class && ((Class) type).isEnum()) { return Enum.valueOf((Class) type, str); } @@ -859,6 +842,14 @@ public class ConfigManager { return Base64.getUrlDecoder().decode(str); } + if (type instanceof ParameterizedType) + { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (parameterizedType.getRawType() == Set.class) + { + return gson.fromJson(str, parameterizedType); + } + } if (type == EnumSet.class) { try @@ -898,7 +889,7 @@ public class ConfigManager } @Nullable - static String objectToString(Object object) + String objectToString(Object object) { if (object instanceof Color) { @@ -945,6 +936,10 @@ public class ConfigManager { return Base64.getUrlEncoder().encodeToString((byte[]) object); } + if (object instanceof Set) + { + return gson.toJson(object, Set.class); + } if (object instanceof EnumSet) { if (((EnumSet) object).size() == 0) @@ -954,7 +949,6 @@ public class ConfigManager return ((EnumSet) object).toArray()[0].getClass().getCanonicalName() + "{" + object.toString() + "}"; } - return object == null ? null : object.toString(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java index 3fc4c112d0..df8c74cf0f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java @@ -24,68 +24,36 @@ */ package net.runelite.client.plugins.config; +import com.google.common.base.MoreObjects; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Sets; import com.google.common.primitives.Ints; -import java.awt.BasicStroke; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GridLayout; + +import java.awt.*; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; +import java.lang.reflect.ParameterizedType; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; -import java.util.function.Consumer; import javax.inject.Inject; -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JFormattedTextField; -import javax.swing.JLabel; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JSlider; -import javax.swing.JSpinner; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.ScrollPaneConstants; -import javax.swing.SpinnerModel; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; +import javax.swing.*; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.MatteBorder; import javax.swing.event.ChangeListener; -import javax.swing.plaf.basic.BasicSpinnerUI; import javax.swing.text.JTextComponent; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.events.ConfigButtonClicked; -import net.runelite.client.config.Button; import net.runelite.client.config.ConfigDescriptor; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; @@ -100,7 +68,6 @@ import net.runelite.client.config.Keybind; import net.runelite.client.config.ModifierlessKeybind; import net.runelite.client.config.Range; import net.runelite.client.config.Units; -import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ExternalPluginsChanged; import net.runelite.client.events.PluginChanged; @@ -115,15 +82,14 @@ import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; import net.runelite.client.ui.components.ColorJButton; import net.runelite.client.ui.components.ComboBoxListRenderer; -import net.runelite.client.ui.components.ToggleButton; import net.runelite.client.ui.components.colorpicker.ColorPickerManager; import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker; import net.runelite.client.util.ColorUtil; -import net.runelite.client.util.DeferredDocumentChangedListener; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.LinkBrowser; import net.runelite.client.util.SwingUtil; import net.runelite.client.util.Text; +import org.apache.commons.lang3.ArrayUtils; @Slf4j class ConfigPanel extends PluginPanel @@ -138,33 +104,6 @@ class ConfigPanel extends PluginPanel private static final Map sectionExpandStates = new HashMap<>(); - private final FixedWidthPanel mainPanel; - private final JLabel title; - private final PluginToggleButton pluginToggle; - - @Inject - private PluginListPanel pluginList; - - @Inject - private ConfigManager configManager; - - @Inject - private PluginManager pluginManager; - - @Inject - private ExternalPluginManager externalPluginManager; - - @Inject - private OPRSExternalPluginManager oprsExternalPluginManager; - - @Inject - private ColorPickerManager colorPickerManager; - - @Inject - private EventBus eventBus; - - private PluginConfigurationDescriptor pluginConfig = null; - static { final BufferedImage backIcon = ImageUtil.loadImageResource(ConfigPanel.class, "config_back_icon.png"); @@ -180,10 +119,36 @@ class ConfigPanel extends PluginPanel SECTION_RETRACT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionExpandIcon, -100)); } - public ConfigPanel() + private final PluginListPanel pluginList; + private final ConfigManager configManager; + private final PluginManager pluginManager; + private final ExternalPluginManager externalPluginManager; + private final ColorPickerManager colorPickerManager; + + private final ListCellRenderer> listCellRenderer = new ComboBoxListRenderer<>(); + + private final FixedWidthPanel mainPanel; + private final JLabel title; + private final PluginToggleButton pluginToggle; + + private PluginConfigurationDescriptor pluginConfig = null; + + @Inject + private OPRSExternalPluginManager oprsExternalPluginManager; + + @Inject + private ConfigPanel(PluginListPanel pluginList, ConfigManager configManager, PluginManager pluginManager, + ExternalPluginManager externalPluginManager, ColorPickerManager colorPickerManager, OPRSExternalPluginManager oprsExternalPluginManager) { super(false); + this.pluginList = pluginList; + this.configManager = configManager; + this.pluginManager = pluginManager; + this.externalPluginManager = externalPluginManager; + this.colorPickerManager = colorPickerManager; + this.oprsExternalPluginManager = oprsExternalPluginManager; + setLayout(new BorderLayout()); setBackground(ColorScheme.DARK_GRAY_COLOR); @@ -462,436 +427,63 @@ class ConfigPanel extends PluginPanel configEntryName.setToolTipText("" + name + ":
" + cid.getItem().description() + ""); PluginListItem.addLabelPopupMenu(configEntryName, createResetMenuItem(pluginConfig, cid)); item.add(configEntryName, BorderLayout.CENTER); - item.setName(cid.getItem().keyName()); - - if (cid.getType() == Button.class) - { - try - { - ConfigItem cidItem = cid.getItem(); - JButton button = new JButton(cidItem.name()); - button.addActionListener((e) -> - { - ConfigButtonClicked event = new ConfigButtonClicked(); - event.setGroup(cd.getGroup().value()); - event.setKey(cid.getItem().keyName()); - eventBus.post(event); - }); - item.add(button); - } - catch (Exception ex) - { - log.error("Adding action listener failed: {}", ex.getMessage()); - ex.printStackTrace(); - } - } if (cid.getType() == boolean.class) { - JCheckBox checkbox = new ToggleButton(); - checkbox.setPreferredSize(new Dimension(26, 25)); - checkbox.setSelected(Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()))); - checkbox.addActionListener(ae -> changeConfiguration(checkbox, cd, cid)); - - item.add(checkbox, BorderLayout.EAST); + item.add(createCheckbox(cd, cid), BorderLayout.EAST); } - - if (cid.getType().isAssignableFrom(Consumer.class)) + else if (cid.getType() == int.class) { - item.remove(configEntryName); - - JButton button = new JButton(cid.getItem().name()); - button.addActionListener((e) -> - { - log.debug("Running consumer: {}.{}", cd.getGroup().value(), cid.getItem().keyName()); - configManager.getConsumer(cd.getGroup().value(), cid.getItem().keyName()).accept(pluginConfig.getPlugin()); - }); - - item.add(button, BorderLayout.CENTER); + item.add(createIntSpinner(cd, cid), BorderLayout.EAST); } - - if (cid.getType() == int.class) - { - int value = Integer.parseInt(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())); - - Units units = cid.getUnits(); - Range range = cid.getRange(); - int min = 0, max = Integer.MAX_VALUE; - if (range != null) - { - min = range.min(); - max = range.max(); - } - - // Config may previously have been out of range - value = Ints.constrainToRange(value, min, max); - - if (max < Integer.MAX_VALUE) - { - JLabel sliderValueLabel = new JLabel(); - JSlider slider = new JSlider(min, max, value); - slider.setBackground(ColorScheme.DARK_GRAY_COLOR); - if (units != null) - { - sliderValueLabel.setText(slider.getValue() + units.value()); - } - else - { - sliderValueLabel.setText(String.valueOf(slider.getValue())); - } - slider.setPreferredSize(new Dimension(80, 25)); - slider.addChangeListener((l) -> - { - if (units != null) - { - sliderValueLabel.setText(slider.getValue() + units.value()); - } - else - { - sliderValueLabel.setText(String.valueOf(slider.getValue())); - } - - if (!slider.getValueIsAdjusting()) - { - changeConfiguration(slider, cd, cid); - } - } - ); - - SpinnerModel model = new SpinnerNumberModel(value, min, max, 1); - JSpinner spinner = new JSpinner(model); - Component editor = spinner.getEditor(); - JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField(); - spinnerTextField.setColumns(SPINNER_FIELD_WIDTH); - spinner.setUI(new BasicSpinnerUI() - { - protected Component createNextButton() - { - return null; - } - - protected Component createPreviousButton() - { - return null; - } - }); - - JPanel subPanel = new JPanel(); - subPanel.setPreferredSize(new Dimension(110, 25)); - subPanel.setLayout(new BorderLayout()); - - spinner.addChangeListener((ce) -> - { - changeConfiguration(spinner, cd, cid); - - if (units != null) - { - sliderValueLabel.setText(spinner.getValue() + units.value()); - } - else - { - sliderValueLabel.setText(String.valueOf(spinner.getValue())); - } - slider.setValue((Integer) spinner.getValue()); - - subPanel.add(sliderValueLabel, BorderLayout.WEST); - subPanel.add(slider, BorderLayout.EAST); - subPanel.remove(spinner); - - validate(); - repaint(); - }); - - sliderValueLabel.addMouseListener(new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - spinner.setValue(slider.getValue()); - - subPanel.remove(sliderValueLabel); - subPanel.remove(slider); - subPanel.add(spinner, BorderLayout.EAST); - - validate(); - repaint(); - - final JTextField tf = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField(); - tf.requestFocusInWindow(); - SwingUtilities.invokeLater(tf::selectAll); - } - }); - - subPanel.add(sliderValueLabel, BorderLayout.WEST); - subPanel.add(slider, BorderLayout.EAST); - - item.add(subPanel, BorderLayout.EAST); - } - else - { - SpinnerModel model = new SpinnerNumberModel(value, min, max, 1); - JSpinner spinner = new JSpinner(model); - Component editor = spinner.getEditor(); - JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField(); - spinnerTextField.setColumns(SPINNER_FIELD_WIDTH); - spinner.addChangeListener(ce -> changeConfiguration(spinner, cd, cid)); - - if (units != null) - { - spinnerTextField.setFormatterFactory(new UnitFormatterFactory(units)); - } - - item.add(spinner, BorderLayout.EAST); - } - } - else if (cid.getType() == double.class) { - double value = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName(), double.class); - - SpinnerModel model = new SpinnerNumberModel(value, 0, Double.MAX_VALUE, 0.1); - JSpinner spinner = new JSpinner(model); - Component editor = spinner.getEditor(); - JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField(); - spinnerTextField.setColumns(SPINNER_FIELD_WIDTH); - spinner.addChangeListener(ce -> changeConfiguration(spinner, cd, cid)); - - item.add(spinner, BorderLayout.EAST); + item.add(createDoubleSpinner(cd, cid), BorderLayout.EAST); } - - if (cid.getType() == String.class) + else if (cid.getType() == String.class) { - JTextComponent textField; - - if (cid.getItem().secret()) - { - textField = new JPasswordField(); - } - else - { - final JTextArea textArea = new JTextArea(); - textArea.setLineWrap(true); - textArea.setWrapStyleWord(true); - textField = textArea; - } - - textField.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - textField.setText(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())); - - textField.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - changeConfiguration(textField, cd, cid); - } - }); - - if (cid.getItem().parse()) - { - JLabel parsingLabel = new JLabel(); - parsingLabel.setHorizontalAlignment(SwingConstants.CENTER); - parsingLabel.setPreferredSize(new Dimension(PANEL_WIDTH, 15)); - - DeferredDocumentChangedListener listener = new DeferredDocumentChangedListener(); - listener.addChangeListener(e -> - { - if (cid.getItem().parse()) - { - parseLabel(cid.getItem(), parsingLabel, textField.getText()); - } - }); - textField.getDocument().addDocumentListener(listener); - - item.add(configEntryName, BorderLayout.NORTH); - item.add(textField, BorderLayout.CENTER); - - - parseLabel(cid.getItem(), parsingLabel, textField.getText()); - item.add(parsingLabel, BorderLayout.SOUTH); - } - else - { - item.add(textField, BorderLayout.SOUTH); - } + item.add(createTextField(cd, cid), BorderLayout.SOUTH); } - - if (cid.getType() == Color.class) + else if (cid.getType() == Color.class) { - Color existing = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName(), Color.class); - - ColorJButton colorPickerBtn; - - boolean alphaHidden = cid.getAlpha() == null; - - if (existing == null) - { - colorPickerBtn = new ColorJButton("Pick a color", Color.BLACK); - } - else - { - String colorHex = "#" + (alphaHidden ? ColorUtil.colorToHexCode(existing) : ColorUtil.colorToAlphaHexCode(existing)).toUpperCase(); - colorPickerBtn = new ColorJButton(colorHex, existing); - } - - colorPickerBtn.setFocusable(false); - colorPickerBtn.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - RuneliteColorPicker colorPicker = colorPickerManager.create( - SwingUtilities.windowForComponent(ConfigPanel.this), - colorPickerBtn.getColor(), - cid.getItem().name(), - alphaHidden); - colorPicker.setLocation(getLocationOnScreen()); - colorPicker.setOnColorChange(c -> - { - colorPickerBtn.setColor(c); - colorPickerBtn.setText("#" + (alphaHidden ? ColorUtil.colorToHexCode(c) : ColorUtil.colorToAlphaHexCode(c)).toUpperCase()); - }); - colorPicker.setOnClose(c -> changeConfiguration(colorPicker, cd, cid)); - colorPicker.setVisible(true); - } - }); - - item.add(colorPickerBtn, BorderLayout.EAST); + item.add(createColorPicker(cd, cid), BorderLayout.EAST); } - - if (cid.getType() == Dimension.class) + else if (cid.getType() == Dimension.class) { - JPanel dimensionPanel = new JPanel(); - dimensionPanel.setLayout(new BorderLayout()); - - String str = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()); - String[] splitStr = str.split("x"); - int width = Integer.parseInt(splitStr[0]); - int height = Integer.parseInt(splitStr[1]); - - SpinnerModel widthModel = new SpinnerNumberModel(width, 0, Integer.MAX_VALUE, 1); - JSpinner widthSpinner = new JSpinner(widthModel); - Component widthEditor = widthSpinner.getEditor(); - JFormattedTextField widthSpinnerTextField = ((JSpinner.DefaultEditor) widthEditor).getTextField(); - widthSpinnerTextField.setColumns(4); - - SpinnerModel heightModel = new SpinnerNumberModel(height, 0, Integer.MAX_VALUE, 1); - JSpinner heightSpinner = new JSpinner(heightModel); - Component heightEditor = heightSpinner.getEditor(); - JFormattedTextField heightSpinnerTextField = ((JSpinner.DefaultEditor) heightEditor).getTextField(); - heightSpinnerTextField.setColumns(4); - - ChangeListener listener = e -> - configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), widthSpinner.getValue() + "x" + heightSpinner.getValue()); - - widthSpinner.addChangeListener(listener); - heightSpinner.addChangeListener(listener); - - dimensionPanel.add(widthSpinner, BorderLayout.WEST); - dimensionPanel.add(new JLabel(" x "), BorderLayout.CENTER); - dimensionPanel.add(heightSpinner, BorderLayout.EAST); - - item.add(dimensionPanel, BorderLayout.EAST); + item.add(createDimension(cd, cid), BorderLayout.EAST); } - - if (cid.getType().isEnum()) + else if (cid.getType() instanceof Class && ((Class) cid.getType()).isEnum()) { - Class type = (Class) cid.getType(); - - JComboBox> box = new JComboBox>(type.getEnumConstants()); // NOPMD: UseDiamondOperator - // set renderer prior to calling box.getPreferredSize(), since it will invoke the renderer - // to build components for each combobox element in order to compute the display size of the - // combobox - box.setRenderer(new ComboBoxListRenderer<>()); - box.setPreferredSize(new Dimension(box.getPreferredSize().width, 25)); - box.setForeground(Color.WHITE); - box.setFocusable(false); - - try - { - Enum selectedItem = Enum.valueOf(type, configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())); - box.setSelectedItem(selectedItem); - box.setToolTipText(Text.titleCase(selectedItem)); - } - catch (IllegalArgumentException ex) - { - log.debug("invalid seleced item", ex); - } - box.addItemListener(e -> - { - if (e.getStateChange() == ItemEvent.SELECTED) - { - changeConfiguration(box, cd, cid); - box.setToolTipText(Text.titleCase((Enum) box.getSelectedItem())); - } - }); - item.add(box, BorderLayout.EAST); + item.add(createComboBox(cd, cid), BorderLayout.EAST); } - - if (cid.getType() == Keybind.class || cid.getType() == ModifierlessKeybind.class) + else if (cid.getType() == Keybind.class || cid.getType() == ModifierlessKeybind.class) { - Keybind startingValue = configManager.getConfiguration(cd.getGroup().value(), - cid.getItem().keyName(), - (Class) cid.getType()); - - HotkeyButton button = new HotkeyButton(startingValue, cid.getType() == ModifierlessKeybind.class); - - button.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - changeConfiguration(button, cd, cid); - } - }); - - item.add(button, BorderLayout.EAST); + item.add(createKeybind(cd, cid), BorderLayout.EAST); } - - if (cid.getType() == EnumSet.class) + else if (cid.getType() instanceof ParameterizedType) { - Class enumType = cid.getItem().enumClass(); - - EnumSet enumSet = configManager.getConfiguration(cd.getGroup().value(), - cid.getItem().keyName(), EnumSet.class); - if (enumSet == null || enumSet.contains(null)) + ParameterizedType parameterizedType = (ParameterizedType) cid.getType(); + if (parameterizedType.getRawType() == Set.class) { - enumSet = EnumSet.noneOf(enumType); + item.add(createList(cd, cid), BorderLayout.EAST); + } + } + else if (cid.getType() instanceof EnumSet) + { + ParameterizedType parameterizedType = (ParameterizedType) cid.getType(); + if (parameterizedType.getRawType() == EnumSet.class) + { + item.add(createList(cd, cid), BorderLayout.EAST); } - - JPanel enumsetLayout = new JPanel(new GridLayout(0, 2)); - List jcheckboxes = new ArrayList<>(); - - for (Object obj : enumType.getEnumConstants()) - { - String option = String.valueOf(obj).toLowerCase().replace("_", " "); - - JCheckBox checkbox = new ToggleButton(option); - checkbox.setBackground(ColorScheme.DARK_GRAY_COLOR); - checkbox.setSelected(enumSet.contains(obj)); - jcheckboxes.add(checkbox); - - enumsetLayout.add(checkbox); - } - - jcheckboxes.forEach(checkbox -> checkbox.addActionListener(ae -> changeConfiguration(jcheckboxes, cd, cid))); - - item.add(enumsetLayout, BorderLayout.SOUTH); } - JPanel section = sectionWidgets.get(cid.getItem().section()); - JPanel title = titleWidgets.get(cid.getItem().title()); - - if (section != null) + if (section == null) { - section.add(item); - } - else if (title != null) - { - title.add(item); + topLevelPanels.put(cid, item); } else { - topLevelPanels.put(cid, item); + section.add(item); } } @@ -927,69 +519,249 @@ class ConfigPanel extends PluginPanel revalidate(); } - private Boolean parse(ConfigItem item, String value) + private JCheckBox createCheckbox(ConfigDescriptor cd, ConfigItemDescriptor cid) { - try - { - Method parse = item.clazz().getMethod(item.method(), String.class); - - return (boolean) parse.invoke(null, value); - } - catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) - { - log.error("Parsing failed: {}", ex.getMessage()); - } - - return null; + JCheckBox checkbox = new JCheckBox(); + checkbox.setBackground(ColorScheme.LIGHT_GRAY_COLOR); + checkbox.setSelected(Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()))); + checkbox.addActionListener(ae -> changeConfiguration(checkbox, cd, cid)); + return checkbox; } - private void parseLabel(ConfigItem item, JLabel label, String value) + private JSpinner createIntSpinner(ConfigDescriptor cd, ConfigItemDescriptor cid) { - Boolean result = parse(item, value); + int value = Integer.parseInt(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())); - if (result == null) + Range range = cid.getRange(); + int min = 0, max = Integer.MAX_VALUE; + if (range != null) { - label.setForeground(Color.RED); - label.setText("Parsing failed"); + min = range.min(); + max = range.max(); } - else if (result) + + // Config may previously have been out of range + value = Ints.constrainToRange(value, min, max); + + SpinnerModel model = new SpinnerNumberModel(value, min, max, 1); + JSpinner spinner = new JSpinner(model); + Component editor = spinner.getEditor(); + JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField(); + spinnerTextField.setColumns(SPINNER_FIELD_WIDTH); + spinner.addChangeListener(ce -> changeConfiguration(spinner, cd, cid)); + + Units units = cid.getUnits(); + if (units != null) { - label.setForeground(Color.GREEN); - label.setText("Valid input"); + spinnerTextField.setFormatterFactory(new UnitFormatterFactory(units)); + } + + return spinner; + } + + private JSpinner createDoubleSpinner(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + double value = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName(), double.class); + + SpinnerModel model = new SpinnerNumberModel(value, 0, Double.MAX_VALUE, 0.1); + JSpinner spinner = new JSpinner(model); + Component editor = spinner.getEditor(); + JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField(); + spinnerTextField.setColumns(SPINNER_FIELD_WIDTH); + spinner.addChangeListener(ce -> changeConfiguration(spinner, cd, cid)); + return spinner; + } + + private JTextComponent createTextField(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + JTextComponent textField; + + if (cid.getItem().secret()) + { + textField = new JPasswordField(); } else { - label.setForeground(Color.RED); - label.setText("Invalid input"); + final JTextArea textArea = new JTextArea(); + textArea.setLineWrap(true); + textArea.setWrapStyleWord(true); + textField = textArea; } - } - private void changeConfiguration(List components, ConfigDescriptor cd, ConfigItemDescriptor cid) - { - Class enumType = cid.getItem().enumClass(); - EnumSet enumSet = configManager.getConfiguration(cd.getGroup().value(), - cid.getItem().keyName(), EnumSet.class); - if (enumSet == null) + textField.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + textField.setText(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())); + + textField.addFocusListener(new FocusAdapter() { - //noinspection unchecked - enumSet = EnumSet.noneOf(enumType); - } - enumSet.clear(); - - EnumSet finalEnumSet = enumSet; - - //noinspection unchecked - components.forEach(value -> - { - if (value.isSelected()) + @Override + public void focusLost(FocusEvent e) { - finalEnumSet.add(Enum.valueOf(cid.getItem().enumClass(), String.valueOf(value.getText()).toUpperCase().replace(" ", "_"))); + changeConfiguration(textField, cd, cid); } }); - configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), finalEnumSet); + return textField; + } - rebuild(); + private ColorJButton createColorPicker(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + Color existing = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName(), Color.class); + + ColorJButton colorPickerBtn; + + boolean alphaHidden = cid.getAlpha() == null; + + if (existing == null) + { + colorPickerBtn = new ColorJButton("Pick a color", Color.BLACK); + } + else + { + String colorHex = "#" + (alphaHidden ? ColorUtil.colorToHexCode(existing) : ColorUtil.colorToAlphaHexCode(existing)).toUpperCase(); + colorPickerBtn = new ColorJButton(colorHex, existing); + } + + colorPickerBtn.setFocusable(false); + colorPickerBtn.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + RuneliteColorPicker colorPicker = colorPickerManager.create( + SwingUtilities.windowForComponent(ConfigPanel.this), + colorPickerBtn.getColor(), + cid.getItem().name(), + alphaHidden); + colorPicker.setLocation(getLocationOnScreen()); + colorPicker.setOnColorChange(c -> + { + colorPickerBtn.setColor(c); + colorPickerBtn.setText("#" + (alphaHidden ? ColorUtil.colorToHexCode(c) : ColorUtil.colorToAlphaHexCode(c)).toUpperCase()); + }); + colorPicker.setOnClose(c -> changeConfiguration(colorPicker, cd, cid)); + colorPicker.setVisible(true); + } + }); + + return colorPickerBtn; + } + + private JPanel createDimension(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + JPanel dimensionPanel = new JPanel(); + dimensionPanel.setLayout(new BorderLayout()); + + String str = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()); + String[] splitStr = str.split("x"); + int width = Integer.parseInt(splitStr[0]); + int height = Integer.parseInt(splitStr[1]); + + SpinnerModel widthModel = new SpinnerNumberModel(width, 0, Integer.MAX_VALUE, 1); + JSpinner widthSpinner = new JSpinner(widthModel); + Component widthEditor = widthSpinner.getEditor(); + JFormattedTextField widthSpinnerTextField = ((JSpinner.DefaultEditor) widthEditor).getTextField(); + widthSpinnerTextField.setColumns(4); + + SpinnerModel heightModel = new SpinnerNumberModel(height, 0, Integer.MAX_VALUE, 1); + JSpinner heightSpinner = new JSpinner(heightModel); + Component heightEditor = heightSpinner.getEditor(); + JFormattedTextField heightSpinnerTextField = ((JSpinner.DefaultEditor) heightEditor).getTextField(); + heightSpinnerTextField.setColumns(4); + + ChangeListener listener = e -> + configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), widthSpinner.getValue() + "x" + heightSpinner.getValue()); + + widthSpinner.addChangeListener(listener); + heightSpinner.addChangeListener(listener); + + dimensionPanel.add(widthSpinner, BorderLayout.WEST); + dimensionPanel.add(new JLabel(" x "), BorderLayout.CENTER); + dimensionPanel.add(heightSpinner, BorderLayout.EAST); + + return dimensionPanel; + } + + private JComboBox> createComboBox(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + Class type = (Class) cid.getType(); + + JComboBox> box = new JComboBox>(type.getEnumConstants()); // NOPMD: UseDiamondOperator + // set renderer prior to calling box.getPreferredSize(), since it will invoke the renderer + // to build components for each combobox element in order to compute the display size of the + // combobox + box.setRenderer(listCellRenderer); + box.setPreferredSize(new Dimension(box.getPreferredSize().width, 25)); + box.setForeground(Color.WHITE); + box.setFocusable(false); + + try + { + Enum selectedItem = Enum.valueOf(type, configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())); + box.setSelectedItem(selectedItem); + box.setToolTipText(Text.titleCase(selectedItem)); + } + catch (IllegalArgumentException ex) + { + log.debug("invalid selected item", ex); + } + box.addItemListener(e -> + { + if (e.getStateChange() == ItemEvent.SELECTED) + { + changeConfiguration(box, cd, cid); + box.setToolTipText(Text.titleCase((Enum) box.getSelectedItem())); + } + }); + + return box; + } + + private HotkeyButton createKeybind(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + Keybind startingValue = configManager.getConfiguration(cd.getGroup().value(), + cid.getItem().keyName(), + (Class) cid.getType()); + + HotkeyButton button = new HotkeyButton(startingValue, cid.getType() == ModifierlessKeybind.class); + + button.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + changeConfiguration(button, cd, cid); + } + }); + + return button; + } + + private JList> createList(ConfigDescriptor cd, ConfigItemDescriptor cid) + { + ParameterizedType parameterizedType = (ParameterizedType) cid.getType(); + Class type = (Class) parameterizedType.getActualTypeArguments()[0]; + Set set = configManager.getConfiguration(cd.getGroup().value(), null, + cid.getItem().keyName(), parameterizedType); + + JList> list = new JList>(type.getEnumConstants()); // NOPMD: UseDiamondOperator + list.setCellRenderer(listCellRenderer); + list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + list.setLayoutOrientation(JList.VERTICAL); + list.setSelectedIndices( + MoreObjects.firstNonNull(set, Collections.emptySet()) + .stream() + .mapToInt(e -> ArrayUtils.indexOf(type.getEnumConstants(), e)) + .toArray()); + list.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + changeConfiguration(list, cd, cid); + } + }); + + return list; } private void changeConfiguration(Component component, ConfigDescriptor cd, ConfigItemDescriptor cid) @@ -1019,11 +791,6 @@ class ConfigPanel extends PluginPanel JSpinner spinner = (JSpinner) component; configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), "" + spinner.getValue()); } - else if (component instanceof JSlider) - { - JSlider slider = (JSlider) component; - configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), slider.getValue()); - } else if (component instanceof JTextComponent) { JTextComponent textField = (JTextComponent) component; @@ -1044,6 +811,13 @@ class ConfigPanel extends PluginPanel HotkeyButton hotkeyButton = (HotkeyButton) component; configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), hotkeyButton.getValue()); } + else if (component instanceof JList) + { + JList list = (JList) component; + List selectedValues = list.getSelectedValuesList(); + + configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), Sets.newHashSet(selectedValues)); + } enableDisable(component, cid); rebuild(); @@ -1121,7 +895,7 @@ class ConfigPanel extends PluginPanel { show = Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().value(), cid2.getItem().keyName())); } - else if (cid2.getType().isEnum()) + else if (cid2.getType() instanceof Class && ((Class) cid2.getType()).isEnum()) { Class type = (Class) cid2.getType(); try