config panel: change plugin search to use plugin name + tags
This commit is contained in:
@@ -29,7 +29,6 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.ItemEvent;
|
||||
@@ -37,11 +36,10 @@ import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -84,30 +82,32 @@ import net.runelite.client.ui.DynamicGridLayout;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.ui.components.ComboBoxListRenderer;
|
||||
import net.runelite.client.ui.components.IconTextField;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
|
||||
@Slf4j
|
||||
public class ConfigPanel extends PluginPanel
|
||||
{
|
||||
private static final int SPINNER_FIELD_WIDTH = 6;
|
||||
|
||||
private static final ImageIcon CONFIG_ICON;
|
||||
private static final ImageIcon CONFIG_ICON_HOVER;
|
||||
private static final ImageIcon ON_SWITCHER;
|
||||
private static final ImageIcon OFF_SWITCHER;
|
||||
private static final ImageIcon SEARCH;
|
||||
|
||||
private static final String RUNELITE_PLUGIN = "RuneLite";
|
||||
private static final String CHAT_COLOR_PLUGIN = "Chat Color";
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final ConfigManager configManager;
|
||||
private final ScheduledExecutorService executorService;
|
||||
private final RuneLiteConfig runeLiteConfig;
|
||||
private final ChatColorConfig chatColorConfig;
|
||||
private final IconTextField searchBar = new IconTextField();
|
||||
private final List<PluginListItem> pluginList = new ArrayList<>();
|
||||
private DisplayMode currentMode = DisplayMode.PLUGIN_LIST;
|
||||
private int scrollBarPosition = 0;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized (ImageIO.class)
|
||||
{
|
||||
BufferedImage configIcon = ImageIO.read(ConfigPanel.class.getResourceAsStream("config_edit_icon.png"));
|
||||
CONFIG_ICON = new ImageIcon(configIcon);
|
||||
CONFIG_ICON_HOVER = new ImageIcon(SwingUtil.grayscaleOffset(configIcon, -100));
|
||||
ON_SWITCHER = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("switchers/on.png")));
|
||||
OFF_SWITCHER = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("switchers/off.png")));
|
||||
SEARCH = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("search.png")));
|
||||
}
|
||||
}
|
||||
@@ -117,16 +117,7 @@ public class ConfigPanel extends PluginPanel
|
||||
}
|
||||
}
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final ConfigManager configManager;
|
||||
private final ScheduledExecutorService executorService;
|
||||
private final RuneLiteConfig runeLiteConfig;
|
||||
private final ChatColorConfig chatColorConfig;
|
||||
private final IconTextField searchBar = new IconTextField();
|
||||
private Map<String, JPanel> children = new TreeMap<>();
|
||||
private int scrollBarPosition = 0;
|
||||
|
||||
public ConfigPanel(PluginManager pluginManager, ConfigManager configManager, ScheduledExecutorService executorService,
|
||||
ConfigPanel(PluginManager pluginManager, ConfigManager configManager, ScheduledExecutorService executorService,
|
||||
RuneLiteConfig runeLiteConfig, ChatColorConfig chatColorConfig)
|
||||
{
|
||||
super();
|
||||
@@ -165,210 +156,55 @@ public class ConfigPanel extends PluginPanel
|
||||
setLayout(new DynamicGridLayout(0, 1, 0, 5));
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
rebuildPluginList();
|
||||
openConfigList();
|
||||
initializePluginList();
|
||||
refreshPluginList();
|
||||
}
|
||||
|
||||
final void rebuildPluginList()
|
||||
private void initializePluginList()
|
||||
{
|
||||
scrollBarPosition = getScrollPane().getVerticalScrollBar().getValue();
|
||||
Map<String, JPanel> newChildren = new TreeMap<>();
|
||||
|
||||
// populate pluginList with all non-hidden plugins
|
||||
pluginManager.getPlugins().stream()
|
||||
.filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden())
|
||||
.sorted(Comparator.comparing(left -> left.getClass().getAnnotation(PluginDescriptor.class).name()))
|
||||
.forEach(plugin ->
|
||||
{
|
||||
final Config pluginConfigProxy = pluginManager.getPluginConfigProxy(plugin);
|
||||
final String pluginName = plugin.getClass().getAnnotation(PluginDescriptor.class).name();
|
||||
final Config config = pluginManager.getPluginConfigProxy(plugin);
|
||||
final ConfigDescriptor configDescriptor = config == null ? null : configManager.getConfigDescriptor(config);
|
||||
|
||||
final JPanel groupPanel = buildGroupPanel();
|
||||
|
||||
JLabel name = new JLabel(pluginName);
|
||||
name.setForeground(Color.WHITE);
|
||||
|
||||
groupPanel.add(name, BorderLayout.CENTER);
|
||||
|
||||
final JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(new GridLayout(1, 2));
|
||||
groupPanel.add(buttonPanel, BorderLayout.LINE_END);
|
||||
|
||||
final JLabel editConfigButton = buildConfigButton(pluginConfigProxy);
|
||||
buttonPanel.add(editConfigButton);
|
||||
|
||||
final JLabel toggleButton = buildToggleButton(plugin);
|
||||
toggleButton.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
buttonPanel.add(toggleButton);
|
||||
|
||||
newChildren.put(pluginName, groupPanel);
|
||||
pluginList.add(new PluginListItem(this, plugin, config, configDescriptor));
|
||||
});
|
||||
|
||||
addCoreConfig(newChildren, "RuneLite", runeLiteConfig);
|
||||
addCoreConfig(newChildren, "Chat Color", chatColorConfig);
|
||||
// add special entries for core client configurations
|
||||
pluginList.add(new PluginListItem(this, runeLiteConfig, configManager.getConfigDescriptor(runeLiteConfig),
|
||||
RUNELITE_PLUGIN, "RuneLite client settings", "client"));
|
||||
pluginList.add(new PluginListItem(this, chatColorConfig, configManager.getConfigDescriptor(chatColorConfig),
|
||||
CHAT_COLOR_PLUGIN, "Recolor chat text", "colour", "messages"));
|
||||
|
||||
children = newChildren;
|
||||
openConfigList();
|
||||
pluginList.sort(Comparator.comparing(PluginListItem::getName));
|
||||
}
|
||||
|
||||
private void addCoreConfig(Map<String, JPanel> newChildren, String configName, Config config)
|
||||
void refreshPluginList()
|
||||
{
|
||||
final JPanel groupPanel = buildGroupPanel();
|
||||
scrollBarPosition = getScrollPane().getVerticalScrollBar().getValue();
|
||||
|
||||
JLabel name = new JLabel(configName);
|
||||
name.setForeground(Color.WHITE);
|
||||
|
||||
groupPanel.add(name, BorderLayout.CENTER);
|
||||
|
||||
final JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(new GridLayout(1, 2));
|
||||
groupPanel.add(buttonPanel, BorderLayout.LINE_END);
|
||||
|
||||
final JLabel editConfigButton = buildConfigButton(config);
|
||||
buttonPanel.add(editConfigButton);
|
||||
|
||||
final JLabel toggleButton = buildToggleButton(null);
|
||||
toggleButton.setVisible(false);
|
||||
buttonPanel.add(toggleButton);
|
||||
|
||||
newChildren.put(configName, groupPanel);
|
||||
}
|
||||
|
||||
private JPanel buildGroupPanel()
|
||||
{
|
||||
// Create base panel for the config button and enabled/disabled button
|
||||
final JPanel groupPanel = new JPanel();
|
||||
groupPanel.setLayout(new BorderLayout(3, 0));
|
||||
groupPanel.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, 20));
|
||||
return groupPanel;
|
||||
}
|
||||
|
||||
private JLabel buildConfigButton(Config config)
|
||||
{
|
||||
// Create edit config button and disable it by default
|
||||
final JLabel editConfigButton = new JLabel(CONFIG_ICON);
|
||||
editConfigButton.setPreferredSize(new Dimension(25, 0));
|
||||
editConfigButton.setVisible(false);
|
||||
|
||||
// If we have configuration proxy enable the button and add edit config listener
|
||||
if (config != null)
|
||||
// update enabled / disabled status of all items
|
||||
pluginList.forEach(listItem ->
|
||||
{
|
||||
final ConfigDescriptor configDescriptor = configManager.getConfigDescriptor(config);
|
||||
final boolean configEmpty = configDescriptor.getItems().stream().allMatch(item -> item.getItem().hidden());
|
||||
|
||||
if (!configEmpty)
|
||||
final Plugin plugin = listItem.getPlugin();
|
||||
if (plugin != null)
|
||||
{
|
||||
editConfigButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
editConfigButton.setIcon(CONFIG_ICON);
|
||||
openGroupConfigPanel(config, configDescriptor, configManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
editConfigButton.setIcon(CONFIG_ICON_HOVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
editConfigButton.setIcon(CONFIG_ICON);
|
||||
}
|
||||
});
|
||||
editConfigButton.setVisible(true);
|
||||
editConfigButton.setToolTipText("Edit plugin configuration");
|
||||
}
|
||||
}
|
||||
|
||||
return editConfigButton;
|
||||
}
|
||||
|
||||
private JLabel buildToggleButton(Plugin plugin)
|
||||
{
|
||||
// Create enabling/disabling button
|
||||
final JLabel toggleButton = new JLabel(ON_SWITCHER);
|
||||
toggleButton.setPreferredSize(new Dimension(25, 0));
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
toggleButton.setEnabled(false);
|
||||
return toggleButton;
|
||||
}
|
||||
|
||||
highlightButton(toggleButton, pluginManager.isPluginEnabled(plugin));
|
||||
|
||||
toggleButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
executorService.submit(() ->
|
||||
{
|
||||
final boolean enabled = pluginManager.isPluginEnabled(plugin);
|
||||
pluginManager.setPluginEnabled(plugin, !enabled);
|
||||
|
||||
try
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
pluginManager.stopPlugin(plugin);
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginManager.startPlugin(plugin);
|
||||
}
|
||||
}
|
||||
catch (PluginInstantiationException ex)
|
||||
{
|
||||
log.warn("Error during starting/stopping plugin {}", plugin.getClass().getSimpleName(), ex);
|
||||
}
|
||||
|
||||
highlightButton(toggleButton, !enabled);
|
||||
});
|
||||
listItem.setPluginEnabled(pluginManager.isPluginEnabled(plugin));
|
||||
}
|
||||
});
|
||||
|
||||
return toggleButton;
|
||||
}
|
||||
|
||||
private void highlightButton(JLabel button, boolean enabled)
|
||||
{
|
||||
button.setIcon(enabled ? ON_SWITCHER : OFF_SWITCHER);
|
||||
button.setToolTipText(enabled ? "Disable plugin" : "Enable plugin");
|
||||
}
|
||||
|
||||
private void onSearchBarChanged()
|
||||
{
|
||||
final String text = searchBar.getText();
|
||||
|
||||
children.values().forEach(this::remove);
|
||||
|
||||
if (text.isEmpty())
|
||||
if (currentMode == DisplayMode.PLUGIN_LIST)
|
||||
{
|
||||
children.values().forEach(this::add);
|
||||
revalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
FuzzySearch.findAndProcess(text, children.keySet(), (k) -> add(children.get(k)));
|
||||
revalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivate()
|
||||
{
|
||||
super.onActivate();
|
||||
if (searchBar.getParent() != null)
|
||||
{
|
||||
searchBar.requestFocusInWindow();
|
||||
openConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
private void openConfigList()
|
||||
{
|
||||
currentMode = DisplayMode.PLUGIN_LIST;
|
||||
removeAll();
|
||||
|
||||
JLabel title = new JLabel("Configuration", SwingConstants.LEFT);
|
||||
@@ -384,62 +220,35 @@ public class ConfigPanel extends PluginPanel
|
||||
scrollbar.getVerticalScrollBar().setValue(scrollBarPosition);
|
||||
}
|
||||
|
||||
private void changeConfiguration(Config config, JComponent component, ConfigDescriptor cd, ConfigItemDescriptor cid)
|
||||
private void onSearchBarChanged()
|
||||
{
|
||||
ConfigItem configItem = cid.getItem();
|
||||
final String text = searchBar.getText();
|
||||
|
||||
if (!Strings.isNullOrEmpty(configItem.warning()))
|
||||
pluginList.forEach(this::remove);
|
||||
|
||||
if (text.isEmpty())
|
||||
{
|
||||
final int result = JOptionPane.showOptionDialog(component, configItem.warning(),
|
||||
"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
|
||||
null, new String[]{"Yes", "No"}, "No");
|
||||
pluginList.forEach(this::add);
|
||||
revalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != JOptionPane.YES_OPTION)
|
||||
// show plugins with keywords that matches all the given search terms
|
||||
final String[] searchTerms = text.toLowerCase().split(" ");
|
||||
pluginList.forEach(listItem ->
|
||||
{
|
||||
if (listItem.matchesSearchTerms(searchTerms))
|
||||
{
|
||||
openGroupConfigPanel(config, cd, configManager);
|
||||
return;
|
||||
add(listItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (component instanceof JCheckBox)
|
||||
{
|
||||
JCheckBox checkbox = (JCheckBox) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), "" + checkbox.isSelected());
|
||||
}
|
||||
|
||||
if (component instanceof JSpinner)
|
||||
{
|
||||
JSpinner spinner = (JSpinner) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), "" + spinner.getValue());
|
||||
}
|
||||
|
||||
if (component instanceof JTextArea)
|
||||
{
|
||||
JTextArea textField = (JTextArea) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), textField.getText());
|
||||
}
|
||||
|
||||
if (component instanceof JColorChooser)
|
||||
{
|
||||
JColorChooser jColorChooser = (JColorChooser) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), String.valueOf(jColorChooser.getColor().getRGB()));
|
||||
}
|
||||
|
||||
if (component instanceof JComboBox)
|
||||
{
|
||||
JComboBox jComboBox = (JComboBox) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), ((Enum) jComboBox.getSelectedItem()).name());
|
||||
}
|
||||
|
||||
if (component instanceof HotkeyButton)
|
||||
{
|
||||
HotkeyButton hotkeyButton = (HotkeyButton) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), hotkeyButton.getValue());
|
||||
}
|
||||
revalidate();
|
||||
}
|
||||
|
||||
private void openGroupConfigPanel(Config config, ConfigDescriptor cd, ConfigManager configManager)
|
||||
void openGroupConfigPanel(Config config, ConfigDescriptor cd)
|
||||
{
|
||||
currentMode = DisplayMode.CONFIG_PANEL;
|
||||
scrollBarPosition = getScrollPane().getVerticalScrollBar().getValue();
|
||||
removeAll();
|
||||
String name = cd.getGroup().name() + " Configuration";
|
||||
@@ -647,7 +456,7 @@ public class ConfigPanel extends PluginPanel
|
||||
configManager.setDefaultConfiguration(config, true);
|
||||
|
||||
// Reload configuration panel
|
||||
openGroupConfigPanel(config, cd, configManager);
|
||||
openGroupConfigPanel(config, cd);
|
||||
});
|
||||
add(resetButton);
|
||||
|
||||
@@ -658,4 +467,112 @@ public class ConfigPanel extends PluginPanel
|
||||
revalidate();
|
||||
getScrollPane().getVerticalScrollBar().setValue(0);
|
||||
}
|
||||
|
||||
private void changeConfiguration(Config config, JComponent component, ConfigDescriptor cd, ConfigItemDescriptor cid)
|
||||
{
|
||||
ConfigItem configItem = cid.getItem();
|
||||
|
||||
if (!Strings.isNullOrEmpty(configItem.warning()))
|
||||
{
|
||||
final int result = JOptionPane.showOptionDialog(component, configItem.warning(),
|
||||
"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
|
||||
null, new String[]{"Yes", "No"}, "No");
|
||||
|
||||
if (result != JOptionPane.YES_OPTION)
|
||||
{
|
||||
openGroupConfigPanel(config, cd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (component instanceof JCheckBox)
|
||||
{
|
||||
JCheckBox checkbox = (JCheckBox) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), "" + checkbox.isSelected());
|
||||
}
|
||||
|
||||
if (component instanceof JSpinner)
|
||||
{
|
||||
JSpinner spinner = (JSpinner) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), "" + spinner.getValue());
|
||||
}
|
||||
|
||||
if (component instanceof JTextArea)
|
||||
{
|
||||
JTextArea textField = (JTextArea) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), textField.getText());
|
||||
}
|
||||
|
||||
if (component instanceof JColorChooser)
|
||||
{
|
||||
JColorChooser jColorChooser = (JColorChooser) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), String.valueOf(jColorChooser.getColor().getRGB()));
|
||||
}
|
||||
|
||||
if (component instanceof JComboBox)
|
||||
{
|
||||
JComboBox jComboBox = (JComboBox) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), ((Enum) jComboBox.getSelectedItem()).name());
|
||||
}
|
||||
|
||||
if (component instanceof HotkeyButton)
|
||||
{
|
||||
HotkeyButton hotkeyButton = (HotkeyButton) component;
|
||||
configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), hotkeyButton.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
void startPlugin(Plugin plugin, PluginListItem listItem)
|
||||
{
|
||||
executorService.submit(() ->
|
||||
{
|
||||
pluginManager.setPluginEnabled(plugin, true);
|
||||
|
||||
try
|
||||
{
|
||||
pluginManager.startPlugin(plugin);
|
||||
}
|
||||
catch (PluginInstantiationException ex)
|
||||
{
|
||||
log.warn("Error when starting plugin {}", plugin.getClass().getSimpleName(), ex);
|
||||
}
|
||||
|
||||
listItem.setPluginEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
void stopPlugin(Plugin plugin, PluginListItem listItem)
|
||||
{
|
||||
executorService.submit(() ->
|
||||
{
|
||||
pluginManager.setPluginEnabled(plugin, false);
|
||||
|
||||
try
|
||||
{
|
||||
pluginManager.stopPlugin(plugin);
|
||||
}
|
||||
catch (PluginInstantiationException ex)
|
||||
{
|
||||
log.warn("Error when stopping plugin {}", plugin.getClass().getSimpleName(), ex);
|
||||
}
|
||||
|
||||
listItem.setPluginEnabled(false);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivate()
|
||||
{
|
||||
super.onActivate();
|
||||
if (searchBar.getParent() != null)
|
||||
{
|
||||
searchBar.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
|
||||
enum DisplayMode
|
||||
{
|
||||
PLUGIN_LIST,
|
||||
CONFIG_PANEL
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,6 @@ public class ConfigPlugin extends Plugin
|
||||
@Subscribe
|
||||
public void onPluginChanged(PluginChanged event)
|
||||
{
|
||||
SwingUtilities.invokeLater(configPanel::rebuildPluginList);
|
||||
SwingUtilities.invokeLater(configPanel::refreshPluginList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Value;
|
||||
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||
|
||||
public class FuzzySearch
|
||||
{
|
||||
private static final JaroWinklerDistance FUZZY_SCORE = new JaroWinklerDistance();
|
||||
private static final double STRING_OCCURRENCE_MULTIPLIER = 3d;
|
||||
|
||||
/**
|
||||
* Try to find a match and call callback on each match, sorted by score and filtered by average
|
||||
*
|
||||
* @param query query to search for
|
||||
* @param entries entries to search in
|
||||
* @param callback callback to call
|
||||
*/
|
||||
public static void findAndProcess(final String query, final Collection<String> entries, final Consumer<String> callback)
|
||||
{
|
||||
// Calculate score for each entry matching query
|
||||
final Set<FuzzyMatch> matches = entries.stream()
|
||||
.map(entry -> new FuzzyMatch(
|
||||
FUZZY_SCORE.apply(query, entry)
|
||||
+ calculateStringOccurrenceBonus(entry, query)
|
||||
* STRING_OCCURRENCE_MULTIPLIER,
|
||||
entry))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Calculate average score of the matches to filter out the less relevant ones
|
||||
final double average = matches.stream().mapToDouble(m -> m.score).average().orElse(0);
|
||||
final double max = matches.stream().mapToDouble(m -> m.score).max().orElse(0);
|
||||
final double limit = Math.min(average * 1.7, max);
|
||||
|
||||
matches.stream()
|
||||
.filter(m -> m.score >= limit)
|
||||
.sorted((left, right) -> Double.compare(right.score, left.score))
|
||||
.map(m -> m.value)
|
||||
.forEach(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates string occurrence bonus of query in the entry string
|
||||
* @param entry entry string
|
||||
* @param query query string
|
||||
* @return string occurrence bonus
|
||||
*/
|
||||
private static double calculateStringOccurrenceBonus(final String entry, final String query)
|
||||
{
|
||||
// Exit early, no occurrence bonus for too long query
|
||||
if (query.length() > entry.length())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create relaxed variants of the input (e.g lower cased ones)
|
||||
final String relaxedEntry = entry.toLowerCase();
|
||||
final String relaxedQuery = query.toLowerCase();
|
||||
|
||||
// Create base bonus
|
||||
final double base = 1d / 6d;
|
||||
|
||||
if (entry.equals(query))
|
||||
{
|
||||
return base * 6d;
|
||||
}
|
||||
if (entry.equals(relaxedQuery) || relaxedQuery.equals(entry))
|
||||
{
|
||||
return base * 5d;
|
||||
}
|
||||
if (relaxedEntry.equals(relaxedQuery))
|
||||
{
|
||||
return base * 4d;
|
||||
}
|
||||
if (entry.contains(query))
|
||||
{
|
||||
return base * 3d;
|
||||
}
|
||||
if (entry.contains(relaxedQuery) || relaxedEntry.contains(query))
|
||||
{
|
||||
return base * 2d;
|
||||
}
|
||||
if (relaxedEntry.contains(relaxedQuery))
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class FuzzyMatch
|
||||
{
|
||||
/**
|
||||
* Score of the match
|
||||
*/
|
||||
double score;
|
||||
|
||||
/**
|
||||
* Match value
|
||||
*/
|
||||
String value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.config;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
import lombok.Getter;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigDescriptor;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||
|
||||
class PluginListItem extends JPanel
|
||||
{
|
||||
private static final JaroWinklerDistance DISTANCE = new JaroWinklerDistance();
|
||||
|
||||
private static final ImageIcon CONFIG_ICON;
|
||||
private static final ImageIcon CONFIG_ICON_HOVER;
|
||||
private static final ImageIcon ON_SWITCHER;
|
||||
private static final ImageIcon OFF_SWITCHER;
|
||||
|
||||
private final ConfigPanel configPanel;
|
||||
private @Getter @Nullable final Plugin plugin;
|
||||
private @Nullable final Config config;
|
||||
private @Nullable final ConfigDescriptor configDescriptor;
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final List<String> keywords = new ArrayList<>();
|
||||
|
||||
private final JLabel configButton = new JLabel(CONFIG_ICON);
|
||||
private final JLabel toggleButton = new JLabel(OFF_SWITCHER);
|
||||
|
||||
private boolean isPluginEnabled = false;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized (ImageIO.class)
|
||||
{
|
||||
BufferedImage configIcon = ImageIO.read(ConfigPanel.class.getResourceAsStream("config_edit_icon.png"));
|
||||
CONFIG_ICON = new ImageIcon(configIcon);
|
||||
CONFIG_ICON_HOVER = new ImageIcon(SwingUtil.grayscaleOffset(configIcon, -100));
|
||||
ON_SWITCHER = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("switchers/on.png")));
|
||||
OFF_SWITCHER = new ImageIcon(ImageIO.read(ConfigPanel.class.getResourceAsStream("switchers/off.png")));
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code PluginListItem} for a plugin.
|
||||
* <p>
|
||||
* Note that {@code config} and {@code configDescriptor} can be {@code null}
|
||||
* if there is no configuration associated with the plugin.
|
||||
*/
|
||||
PluginListItem(ConfigPanel configPanel, Plugin plugin, @Nullable Config config,
|
||||
@Nullable ConfigDescriptor configDescriptor)
|
||||
{
|
||||
final PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class);
|
||||
|
||||
this.configPanel = configPanel;
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.configDescriptor = configDescriptor;
|
||||
this.name = descriptor.name();
|
||||
this.description = descriptor.description();
|
||||
Collections.addAll(keywords, name.toLowerCase().split(" "));
|
||||
Collections.addAll(keywords, descriptor.tags());
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code PluginListItem} for a core configuration.
|
||||
*/
|
||||
PluginListItem(ConfigPanel configPanel, Config config, ConfigDescriptor configDescriptor,
|
||||
String name, String description, String... tags)
|
||||
{
|
||||
this.configPanel = configPanel;
|
||||
this.plugin = null;
|
||||
this.config = config;
|
||||
this.configDescriptor = configDescriptor;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
Collections.addAll(keywords, name.toLowerCase().split(" "));
|
||||
Collections.addAll(keywords, tags);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize()
|
||||
{
|
||||
setLayout(new BorderLayout(3, 0));
|
||||
setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, 20));
|
||||
|
||||
JLabel nameLabel = new JLabel(name);
|
||||
nameLabel.setForeground(Color.WHITE);
|
||||
|
||||
if (!description.isEmpty())
|
||||
{
|
||||
nameLabel.setToolTipText("<html>" + name + ":<br>" + description + "</html>");
|
||||
}
|
||||
|
||||
add(nameLabel, BorderLayout.CENTER);
|
||||
|
||||
final JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(new GridLayout(1, 2));
|
||||
add(buttonPanel, BorderLayout.LINE_END);
|
||||
|
||||
configButton.setPreferredSize(new Dimension(25, 0));
|
||||
configButton.setVisible(false);
|
||||
attachConfigButtonListener();
|
||||
buttonPanel.add(configButton);
|
||||
|
||||
toggleButton.setPreferredSize(new Dimension(25, 0));
|
||||
toggleButton.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
attachToggleButtonListener();
|
||||
buttonPanel.add(toggleButton);
|
||||
}
|
||||
|
||||
private void attachConfigButtonListener()
|
||||
{
|
||||
// no need for a listener if there are no config item to show
|
||||
if (config == null || configDescriptor.getItems().stream().allMatch(item -> item.getItem().hidden()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
configButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
configButton.setIcon(CONFIG_ICON);
|
||||
configPanel.openGroupConfigPanel(config, configDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
configButton.setIcon(CONFIG_ICON_HOVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
configButton.setIcon(CONFIG_ICON);
|
||||
}
|
||||
});
|
||||
configButton.setVisible(true);
|
||||
configButton.setToolTipText("Edit plugin configuration");
|
||||
}
|
||||
|
||||
private void attachToggleButtonListener()
|
||||
{
|
||||
// no need for a listener if there is no plugin to enable / disable
|
||||
if (plugin == null)
|
||||
{
|
||||
toggleButton.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
toggleButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
if (isPluginEnabled)
|
||||
{
|
||||
configPanel.stopPlugin(plugin, PluginListItem.this);
|
||||
}
|
||||
else
|
||||
{
|
||||
configPanel.startPlugin(plugin, PluginListItem.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setPluginEnabled(boolean enabled)
|
||||
{
|
||||
isPluginEnabled = enabled;
|
||||
toggleButton.setIcon(enabled ? ON_SWITCHER : OFF_SWITCHER);
|
||||
toggleButton.setToolTipText(enabled ? "Disable plugin" : "Enable plugin");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all the search terms in the given list matches at least one keyword.
|
||||
* @return true if all search terms matches at least one keyword, or false if otherwise.
|
||||
*/
|
||||
boolean matchesSearchTerms(String[] searchTerms)
|
||||
{
|
||||
for (String term : searchTerms)
|
||||
{
|
||||
if (keywords.stream().noneMatch((t) -> t.contains(term) ||
|
||||
DISTANCE.apply(t, term) > 0.9))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user