From 4148b5e4a849b03616364b766dbfda181a015f1e Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 22 Nov 2019 09:14:12 -0700 Subject: [PATCH] config: Refactor config panel into separate panels for each logical view --- .../client/plugins/config/ConfigPanel.java | 388 ++++-------------- .../client/plugins/config/ConfigPlugin.java | 39 +- .../plugins/config/FixedWidthPanel.java | 39 ++ .../client/plugins/config/HotkeyButton.java | 2 +- .../config/PluginConfigurationDescriptor.java | 69 ++++ .../client/plugins/config/PluginListItem.java | 213 +++------- .../plugins/config/PluginListPanel.java | 356 ++++++++++++++++ .../plugins/config/PluginToggleButton.java | 62 +++ .../client/ui/MultiplexingPluginPanel.java | 134 ++++++ .../net/runelite/client/ui/PluginPanel.java | 6 +- .../net/runelite/client/util/SwingUtil.java | 16 + 11 files changed, 844 insertions(+), 480 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/config/FixedWidthPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/config/PluginConfigurationDescriptor.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/MultiplexingPluginPanel.java 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 bd12d73470..4ed399b78a 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 @@ -36,12 +36,7 @@ import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.ScheduledExecutorService; -import java.util.stream.Collectors; +import javax.inject.Inject; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -61,66 +56,53 @@ import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeListener; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; import lombok.extern.slf4j.Slf4j; -import net.runelite.client.config.ChatColorConfig; -import net.runelite.client.config.Config; import net.runelite.client.config.ConfigDescriptor; -import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; import net.runelite.client.config.ConfigItemDescriptor; import net.runelite.client.config.ConfigManager; import net.runelite.client.config.Keybind; import net.runelite.client.config.ModifierlessKeybind; import net.runelite.client.config.Range; -import net.runelite.client.config.RuneLiteConfig; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.PluginInstantiationException; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.PluginChanged; import net.runelite.client.plugins.PluginManager; import net.runelite.client.ui.ColorScheme; 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.IconButton; -import net.runelite.client.ui.components.IconTextField; 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.ImageUtil; +import net.runelite.client.util.SwingUtil; import net.runelite.client.util.Text; @Slf4j -public class ConfigPanel extends PluginPanel +class ConfigPanel extends PluginPanel { private static final int SPINNER_FIELD_WIDTH = 6; - private static final int SCROLLBAR_WIDTH = 17; - private static final int OFFSET = 6; private static final ImageIcon BACK_ICON; private static final ImageIcon BACK_ICON_HOVER; - private static final String RUNELITE_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value(); - private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins"; - private static final String RUNELITE_PLUGIN = "RuneLite"; - private static final String CHAT_COLOR_PLUGIN = "Chat Color"; + private final FixedWidthPanel mainPanel; + private final JLabel title; + private final PluginToggleButton pluginToggle; - private final PluginManager pluginManager; - private final ConfigManager configManager; - private final ScheduledExecutorService executorService; - private final RuneLiteConfig runeLiteConfig; - private final ChatColorConfig chatColorConfig; - private final ColorPickerManager colorPickerManager; - private final List pluginList = new ArrayList<>(); + @Inject + private PluginListPanel pluginList; - private final IconTextField searchBar = new IconTextField(); - private final JPanel topPanel; - private final JPanel mainPanel; - private final JScrollPane scrollPane; + @Inject + private ConfigManager configManager; - private boolean showingPluginList = true; - private int scrollBarPosition = 0; + @Inject + private PluginManager pluginManager; + + @Inject + private ColorPickerManager colorPickerManager; + + private PluginConfigurationDescriptor pluginConfig = null; static { @@ -129,48 +111,16 @@ public class ConfigPanel extends PluginPanel BACK_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backIcon, -100)); } - ConfigPanel(PluginManager pluginManager, ConfigManager configManager, ScheduledExecutorService executorService, - RuneLiteConfig runeLiteConfig, ChatColorConfig chatColorConfig, ColorPickerManager colorPickerManager) + public ConfigPanel() { super(false); - this.pluginManager = pluginManager; - this.configManager = configManager; - this.executorService = executorService; - this.runeLiteConfig = runeLiteConfig; - this.chatColorConfig = chatColorConfig; - this.colorPickerManager = colorPickerManager; - - searchBar.setIcon(IconTextField.Icon.SEARCH); - searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30)); - searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); - searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); - searchBar.getDocument().addDocumentListener(new DocumentListener() - { - @Override - public void insertUpdate(DocumentEvent e) - { - onSearchBarChanged(); - } - - @Override - public void removeUpdate(DocumentEvent e) - { - onSearchBarChanged(); - } - - @Override - public void changedUpdate(DocumentEvent e) - { - onSearchBarChanged(); - } - }); setLayout(new BorderLayout()); setBackground(ColorScheme.DARK_GRAY_COLOR); - topPanel = new JPanel(); + JPanel topPanel = new JPanel(); topPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - topPanel.setLayout(new BorderLayout(0, OFFSET)); + topPanel.setLayout(new BorderLayout(0, BORDER_OFFSET)); add(topPanel, BorderLayout.NORTH); mainPanel = new FixedWidthPanel(); @@ -182,139 +132,66 @@ public class ConfigPanel extends PluginPanel northPanel.setLayout(new BorderLayout()); northPanel.add(mainPanel, BorderLayout.NORTH); - scrollPane = new JScrollPane(northPanel); + JScrollPane scrollPane = new JScrollPane(northPanel); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); add(scrollPane, BorderLayout.CENTER); - initializePluginList(); - refreshPluginList(); - } - - private void initializePluginList() - { - final List pinnedPlugins = getPinnedPluginNames(); - - // populate pluginList with all non-hidden plugins - pluginManager.getPlugins().stream() - .filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden()) - .forEach(plugin -> - { - final PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); - final Config config = pluginManager.getPluginConfigProxy(plugin); - final ConfigDescriptor configDescriptor = config == null ? null : configManager.getConfigDescriptor(config); - - final PluginListItem listItem = new PluginListItem(this, plugin, descriptor, config, configDescriptor); - listItem.setPinned(pinnedPlugins.contains(listItem.getName())); - pluginList.add(listItem); - }); - - // add special entries for core client configurations - final PluginListItem runeLite = new PluginListItem(this, runeLiteConfig, - configManager.getConfigDescriptor(runeLiteConfig), - RUNELITE_PLUGIN, "RuneLite client settings", "client"); - runeLite.setPinned(pinnedPlugins.contains(RUNELITE_PLUGIN)); - pluginList.add(runeLite); - - final PluginListItem chatColor = new PluginListItem(this, chatColorConfig, - configManager.getConfigDescriptor(chatColorConfig), - CHAT_COLOR_PLUGIN, "Recolor chat text", "colour", "messages"); - chatColor.setPinned(pinnedPlugins.contains(CHAT_COLOR_PLUGIN)); - pluginList.add(chatColor); - - pluginList.sort(Comparator.comparing(PluginListItem::getName)); - } - - void refreshPluginList() - { - // update enabled / disabled status of all items - pluginList.forEach(listItem -> - { - final Plugin plugin = listItem.getPlugin(); - if (plugin != null) - { - listItem.setPluginEnabled(pluginManager.isPluginEnabled(plugin)); - } - }); - - if (showingPluginList) - { - openConfigList(); - } - } - - void openConfigList() - { - if (showingPluginList) - { - scrollBarPosition = scrollPane.getVerticalScrollBar().getValue(); - } - - showingPluginList = true; - - topPanel.removeAll(); - mainPanel.removeAll(); - topPanel.add(searchBar, BorderLayout.CENTER); - - onSearchBarChanged(); - searchBar.requestFocusInWindow(); - validate(); - scrollPane.getVerticalScrollBar().setValue(scrollBarPosition); - } - - private void onSearchBarChanged() - { - final String text = searchBar.getText(); - - pluginList.forEach(mainPanel::remove); - - showMatchingPlugins(true, text); - showMatchingPlugins(false, text); - - revalidate(); - } - - private void showMatchingPlugins(boolean pinned, String text) - { - if (text.isEmpty()) - { - pluginList.stream().filter(item -> pinned == item.isPinned()).forEach(mainPanel::add); - return; - } - - final String[] searchTerms = text.toLowerCase().split(" "); - pluginList.forEach(listItem -> - { - if (pinned == listItem.isPinned() && listItem.matchesSearchTerms(searchTerms)) - { - mainPanel.add(listItem); - } - }); - } - - void openGroupConfigPanel(PluginListItem listItem, Config config, ConfigDescriptor cd) - { - showingPluginList = false; - - scrollBarPosition = scrollPane.getVerticalScrollBar().getValue(); - topPanel.removeAll(); - mainPanel.removeAll(); - - final IconButton topPanelBackButton = new IconButton(BACK_ICON, BACK_ICON_HOVER); + JButton topPanelBackButton = new JButton(BACK_ICON); + topPanelBackButton.setRolloverIcon(BACK_ICON_HOVER); + SwingUtil.removeButtonDecorations(topPanelBackButton); topPanelBackButton.setPreferredSize(new Dimension(22, 0)); topPanelBackButton.setBorder(new EmptyBorder(0, 0, 0, 5)); - topPanelBackButton.addActionListener(e -> openConfigList()); + topPanelBackButton.addActionListener(e -> pluginList.getMuxer().popState()); topPanelBackButton.setToolTipText("Back"); topPanel.add(topPanelBackButton, BorderLayout.WEST); - topPanel.add(listItem.getConfigToggleButton(), BorderLayout.EAST); - - String name = listItem.getName(); - JLabel title = new JLabel(name); + pluginToggle = new PluginToggleButton(); + topPanel.add(pluginToggle, BorderLayout.EAST); + title = new JLabel(); title.setForeground(Color.WHITE); - title.setToolTipText("" + name + ":
" + listItem.getDescription() + ""); - PluginListItem.addLabelPopupMenu(title, PluginListItem.wikiLinkMenuItem(listItem.getName())); - topPanel.add(title); + topPanel.add(title); + } + + void init(PluginConfigurationDescriptor pluginConfig) + { + assert this.pluginConfig == null; + this.pluginConfig = pluginConfig; + + String name = pluginConfig.getName(); + title.setText(name); + title.setForeground(Color.WHITE); + title.setToolTipText("" + name + ":
" + pluginConfig.getDescription() + ""); + PluginListItem.addLabelPopupMenu(title, pluginConfig.createSupportMenuItem()); + + if (pluginConfig.getPlugin() != null) + { + pluginToggle.setSelected(pluginManager.isPluginEnabled(pluginConfig.getPlugin())); + pluginToggle.addItemListener(i -> + { + if (pluginToggle.isSelected()) + { + pluginList.startPlugin(pluginConfig.getPlugin()); + } + else + { + pluginList.stopPlugin(pluginConfig.getPlugin()); + } + }); + } + else + { + pluginToggle.setVisible(false); + } + + rebuild(); + } + + private void rebuild() + { + mainPanel.removeAll(); + + ConfigDescriptor cd = pluginConfig.getConfigDescriptor(); for (ConfigItemDescriptor cid : cd.getItems()) { if (cid.getItem().hidden()) @@ -325,7 +202,7 @@ public class ConfigPanel extends PluginPanel JPanel item = new JPanel(); item.setLayout(new BorderLayout()); item.setMinimumSize(new Dimension(PANEL_WIDTH, 0)); - name = cid.getItem().name(); + String name = cid.getItem().name(); JLabel configEntryName = new JLabel(name); configEntryName.setForeground(Color.WHITE); configEntryName.setToolTipText("" + name + ":
" + cid.getItem().description() + ""); @@ -336,7 +213,7 @@ public class ConfigPanel extends PluginPanel 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(listItem, config, checkbox, cd, cid)); + checkbox.addActionListener(ae -> changeConfiguration(checkbox, cd, cid)); item.add(checkbox, BorderLayout.EAST); } @@ -361,7 +238,7 @@ public class ConfigPanel extends PluginPanel Component editor = spinner.getEditor(); JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField(); spinnerTextField.setColumns(SPINNER_FIELD_WIDTH); - spinner.addChangeListener(ce -> changeConfiguration(listItem, config, spinner, cd, cid)); + spinner.addChangeListener(ce -> changeConfiguration(spinner, cd, cid)); item.add(spinner, BorderLayout.EAST); } @@ -390,7 +267,7 @@ public class ConfigPanel extends PluginPanel @Override public void focusLost(FocusEvent e) { - changeConfiguration(listItem, config, textField, cd, cid); + changeConfiguration(textField, cd, cid); } }); @@ -433,7 +310,7 @@ public class ConfigPanel extends PluginPanel colorPickerBtn.setBackground(c); colorPickerBtn.setText(ColorUtil.toHexColor(c).toUpperCase()); }); - colorPicker.setOnClose(c -> changeConfiguration(listItem, config, colorPicker, cd, cid)); + colorPicker.setOnClose(c -> changeConfiguration(colorPicker, cd, cid)); colorPicker.setVisible(true); } }); @@ -499,7 +376,7 @@ public class ConfigPanel extends PluginPanel { if (e.getStateChange() == ItemEvent.SELECTED) { - changeConfiguration(listItem, config, box, cd, cid); + changeConfiguration(box, cd, cid); box.setToolTipText(Text.titleCase((Enum) box.getSelectedItem())); } }); @@ -519,7 +396,7 @@ public class ConfigPanel extends PluginPanel @Override public void focusLost(FocusEvent e) { - changeConfiguration(listItem, config, button, cd, cid); + changeConfiguration(button, cd, cid); } }); @@ -538,23 +415,21 @@ public class ConfigPanel extends PluginPanel if (result == JOptionPane.YES_OPTION) { - configManager.setDefaultConfiguration(config, true); + configManager.setDefaultConfiguration(pluginConfig.getConfig(), true); - // Reload configuration panel - openGroupConfigPanel(listItem, config, cd); + rebuild(); } }); mainPanel.add(resetButton); JButton backButton = new JButton("Back"); - backButton.addActionListener(e -> openConfigList()); + backButton.addActionListener(e -> pluginList.getMuxer().popState()); mainPanel.add(backButton); revalidate(); - scrollPane.getVerticalScrollBar().setValue(0); } - private void changeConfiguration(PluginListItem listItem, Config config, Component component, ConfigDescriptor cd, ConfigItemDescriptor cid) + private void changeConfiguration(Component component, ConfigDescriptor cd, ConfigItemDescriptor cid) { final ConfigItem configItem = cid.getItem(); @@ -566,7 +441,7 @@ public class ConfigPanel extends PluginPanel if (result != JOptionPane.YES_OPTION) { - openGroupConfigPanel(listItem, config, cd); + rebuild(); return; } } @@ -603,102 +478,21 @@ public class ConfigPanel extends PluginPanel } } - 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); - }); - } - - private List getPinnedPluginNames() - { - final String config = configManager.getConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY); - - if (config == null) - { - return Collections.emptyList(); - } - - return Text.fromCSV(config); - } - - void savePinnedPlugins() - { - final String value = pluginList.stream() - .filter(PluginListItem::isPinned) - .map(PluginListItem::getName) - .collect(Collectors.joining(",")); - - configManager.setConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY, value); - } - - void openConfigurationPanel(String configGroup) - { - for (PluginListItem pluginListItem : pluginList) - { - if (pluginListItem.getName().equals(configGroup)) - { - openGroupConfigPanel(pluginListItem, pluginListItem.getConfig(), pluginListItem.getConfigDescriptor()); - break; - } - } - } - - @Override - public void onActivate() - { - super.onActivate(); - - if (searchBar.getParent() != null) - { - searchBar.requestFocusInWindow(); - } - } - @Override public Dimension getPreferredSize() { return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height); } - private class FixedWidthPanel extends JPanel + @Subscribe + public void onPluginChanged(PluginChanged event) { - @Override - public Dimension getPreferredSize() + if (event.getPlugin() == this.pluginConfig.getPlugin()) { - return new Dimension(PANEL_WIDTH, super.getPreferredSize().height); + SwingUtilities.invokeLater(() -> + { + pluginToggle.setSelected(event.isLoaded()); + }); } - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java index f17542447b..3024dfc786 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java @@ -25,8 +25,8 @@ package net.runelite.client.plugins.config; import java.awt.image.BufferedImage; -import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; +import javax.inject.Provider; import javax.swing.SwingUtilities; import net.runelite.api.MenuAction; import net.runelite.client.config.ChatColorConfig; @@ -34,13 +34,10 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.OverlayMenuClicked; -import net.runelite.client.events.PluginChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.PluginManager; import net.runelite.client.ui.ClientToolbar; import net.runelite.client.ui.NavigationButton; -import net.runelite.client.ui.components.colorpicker.ColorPickerManager; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.util.ImageUtil; @@ -55,31 +52,35 @@ public class ConfigPlugin extends Plugin @Inject private ClientToolbar clientToolbar; + @Inject + private Provider pluginListPanelProvider; + @Inject private ConfigManager configManager; - @Inject - private PluginManager pluginManager; - - @Inject - private ScheduledExecutorService executorService; - @Inject private RuneLiteConfig runeLiteConfig; @Inject private ChatColorConfig chatColorConfig; - @Inject - private ColorPickerManager colorPickerManager; + private PluginListPanel pluginListPanel; - private ConfigPanel configPanel; private NavigationButton navButton; @Override protected void startUp() throws Exception { - configPanel = new ConfigPanel(pluginManager, configManager, executorService, runeLiteConfig, chatColorConfig, colorPickerManager); + pluginListPanel = pluginListPanelProvider.get(); + pluginListPanel.addFakePlugin(new PluginConfigurationDescriptor( + "RuneLite", "RuneLite client settings", new String[]{"client"}, + null, runeLiteConfig, configManager.getConfigDescriptor(runeLiteConfig) + ), + new PluginConfigurationDescriptor( + "Chat Color", "Recolor chat text", new String[]{"colour", "messages"}, + null, chatColorConfig, configManager.getConfigDescriptor(chatColorConfig) + )); + pluginListPanel.rebuildPluginList(); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "config_icon.png"); @@ -87,7 +88,7 @@ public class ConfigPlugin extends Plugin .tooltip("Configuration") .icon(icon) .priority(0) - .panel(configPanel) + .panel(pluginListPanel.getMuxer()) .build(); clientToolbar.addNavigation(navButton); @@ -99,12 +100,6 @@ public class ConfigPlugin extends Plugin clientToolbar.removeNavigation(navButton); } - @Subscribe - public void onPluginChanged(PluginChanged event) - { - SwingUtilities.invokeLater(configPanel::refreshPluginList); - } - @Subscribe public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked) { @@ -126,7 +121,7 @@ public class ConfigPlugin extends Plugin { navButton.getOnSelect().run(); } - configPanel.openConfigurationPanel(descriptor.name()); + pluginListPanel.openConfigurationPanel(descriptor.name()); }); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/FixedWidthPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/FixedWidthPanel.java new file mode 100644 index 0000000000..0a6373a201 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/FixedWidthPanel.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, Adam + * 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.Dimension; +import javax.swing.JPanel; +import net.runelite.client.ui.PluginPanel; + +class FixedWidthPanel extends JPanel +{ + @Override + public Dimension getPreferredSize() + { + return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java index 585ade5ea6..17b23a5785 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java @@ -31,7 +31,7 @@ import lombok.Getter; import net.runelite.client.config.Keybind; import net.runelite.client.config.ModifierlessKeybind; -public class HotkeyButton extends JButton +class HotkeyButton extends JButton { @Getter private Keybind value; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginConfigurationDescriptor.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginConfigurationDescriptor.java new file mode 100644 index 0000000000..69932e1d61 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginConfigurationDescriptor.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Abex + * 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 javax.annotation.Nullable; +import javax.swing.JMenuItem; +import lombok.Value; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigDescriptor; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.util.LinkBrowser; + +@Value +class PluginConfigurationDescriptor +{ + private final String name; + private final String description; + private final String[] tags; + + // Can be null if its not an actual plugin (RuneLite / ChatColors) + @Nullable + private final Plugin plugin; + + // Can be null if it has no more configuration than the on/off toggle + @Nullable + private final Config config; + + @Nullable + private final ConfigDescriptor configDescriptor; + + boolean hasConfigurables() + { + return configDescriptor != null && !configDescriptor.getItems().stream().allMatch(item -> item.getItem().hidden()); + } + + /** + * Creates a menu item for linking to a support page for the plugin + * + * @return A {@link JMenuItem} which opens the plugin's wiki page URL in the browser when clicked + */ + JMenuItem createSupportMenuItem() + { + final JMenuItem menuItem = new JMenuItem("Wiki"); + menuItem.addActionListener(e -> LinkBrowser.browse("https://github.com/runelite/runelite/wiki/" + name.replace(' ', '-'))); + return menuItem; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java index 69dec09365..340e0982ba 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java @@ -38,91 +38,49 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; import javax.swing.ImageIcon; +import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; +import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; -import lombok.AccessLevel; 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.ColorScheme; import net.runelite.client.ui.PluginPanel; -import net.runelite.client.ui.components.IconButton; import net.runelite.client.util.ImageUtil; -import net.runelite.client.util.LinkBrowser; +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 String RUNELITE_WIKI_FORMAT = "https://github.com/runelite/runelite/wiki/%s"; 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 ON_STAR; private static final ImageIcon OFF_STAR; - private final ConfigPanel configPanel; + private final PluginListPanel pluginListPanel; @Getter - @Nullable - private final Plugin plugin; - - @Nullable - @Getter(AccessLevel.PACKAGE) - private final Config config; - - @Nullable - @Getter(AccessLevel.PACKAGE) - private final ConfigDescriptor configDescriptor; - - @Getter - private final String name; - - @Getter - private final String description; - - @Getter - private final IconButton configToggleButton; + private final PluginConfigurationDescriptor pluginConfig; private final List keywords = new ArrayList<>(); - private final IconButton pinButton = new IconButton(OFF_STAR); - private final IconButton configButton = new IconButton(CONFIG_ICON, CONFIG_ICON_HOVER); - private final IconButton toggleButton; - - private boolean isPluginEnabled = false; - - @Getter - private boolean isPinned = false; + private final JToggleButton pinButton; + private final JToggleButton onOffToggle; static { BufferedImage configIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "config_edit_icon.png"); - BufferedImage onSwitcher = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "switcher_on.png"); BufferedImage onStar = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "star_on.png"); CONFIG_ICON = new ImageIcon(configIcon); - ON_SWITCHER = new ImageIcon(onSwitcher); ON_STAR = new ImageIcon(onStar); CONFIG_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(configIcon, -100)); - BufferedImage offSwitcherImage = ImageUtil.flipImage( - ImageUtil.grayscaleOffset( - ImageUtil.grayscaleImage(onSwitcher), - 0.61f - ), - true, - false - ); - OFF_SWITCHER = new ImageIcon(offSwitcherImage); + BufferedImage offStar = ImageUtil.grayscaleOffset( ImageUtil.grayscaleImage(onStar), 0.77f @@ -130,76 +88,54 @@ class PluginListItem extends JPanel OFF_STAR = new ImageIcon(offStar); } - /** - * Creates a new {@code PluginListItem} for a plugin. - *

- * 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, PluginDescriptor descriptor, - @Nullable Config config, @Nullable ConfigDescriptor configDescriptor) + PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig) { - this(configPanel, plugin, config, configDescriptor, - descriptor.name(), descriptor.description(), descriptor.tags()); - } + this.pluginListPanel = pluginListPanel; + this.pluginConfig = pluginConfig; - /** - * Creates a new {@code PluginListItem} for a core configuration. - */ - PluginListItem(ConfigPanel configPanel, Config config, ConfigDescriptor configDescriptor, - String name, String description, String... tags) - { - this(configPanel, null, config, configDescriptor, name, description, tags); - } - - private PluginListItem(ConfigPanel configPanel, @Nullable Plugin plugin, @Nullable Config config, - @Nullable ConfigDescriptor configDescriptor, String name, String description, String... tags) - { - this.configPanel = configPanel; - this.plugin = plugin; - this.config = config; - this.configDescriptor = configDescriptor; - this.name = name; - this.description = description; - Collections.addAll(keywords, name.toLowerCase().split(" ")); - Collections.addAll(keywords, description.toLowerCase().split(" ")); - Collections.addAll(keywords, tags); + Collections.addAll(keywords, pluginConfig.getName().toLowerCase().split(" ")); + Collections.addAll(keywords, pluginConfig.getDescription().toLowerCase().split(" ")); + Collections.addAll(keywords, pluginConfig.getTags()); final List popupMenuItems = new ArrayList<>(); setLayout(new BorderLayout(3, 0)); setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, 20)); - JLabel nameLabel = new JLabel(name); + JLabel nameLabel = new JLabel(pluginConfig.getName()); nameLabel.setForeground(Color.WHITE); - if (!description.isEmpty()) + if (!pluginConfig.getDescription().isEmpty()) { - nameLabel.setToolTipText("" + name + ":
" + description + ""); + nameLabel.setToolTipText("" + pluginConfig.getName() + ":
" + pluginConfig.getDescription() + ""); } - + pinButton = new JToggleButton(OFF_STAR); + pinButton.setSelectedIcon(ON_STAR); + SwingUtil.removeButtonDecorations(pinButton); + SwingUtil.addModalTooltip(pinButton, "Unpin plugin", "Pin plugin"); pinButton.setPreferredSize(new Dimension(21, 0)); add(pinButton, BorderLayout.LINE_START); pinButton.addActionListener(e -> { - setPinned(!isPinned); - configPanel.savePinnedPlugins(); - configPanel.openConfigList(); + pluginListPanel.savePinnedPlugins(); + pluginListPanel.refresh(); }); 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); - buttonPanel.add(configButton); - - // add a listener to configButton only if there are config items to show - if (config != null && !configDescriptor.getItems().stream().allMatch(item -> item.getItem().hidden())) + if (pluginConfig.hasConfigurables()) { + JButton configButton = new JButton(CONFIG_ICON); + configButton.setRolloverIcon(CONFIG_ICON_HOVER); + SwingUtil.removeButtonDecorations(configButton); + configButton.setPreferredSize(new Dimension(25, 0)); + configButton.setVisible(false); + buttonPanel.add(configButton); + configButton.addActionListener(e -> { configButton.setIcon(CONFIG_ICON); @@ -214,72 +150,50 @@ class PluginListItem extends JPanel popupMenuItems.add(configMenuItem); } - popupMenuItems.add(wikiLinkMenuItem(name)); + popupMenuItems.add(pluginConfig.createSupportMenuItem()); addLabelPopupMenu(nameLabel, popupMenuItems); add(nameLabel, BorderLayout.CENTER); - toggleButton = createToggleButton(); - buttonPanel.add(toggleButton); - - configToggleButton = createToggleButton(); - } - - private void attachToggleButtonListener(IconButton button) - { - // no need for a listener if there is no plugin to enable / disable - if (plugin == null) + onOffToggle = new PluginToggleButton(); + buttonPanel.add(onOffToggle); + if (pluginConfig.getPlugin() != null) { - button.setVisible(false); - return; + onOffToggle.addItemListener(i -> + { + if (onOffToggle.isSelected()) + { + pluginListPanel.startPlugin(pluginConfig.getPlugin()); + } + else + { + pluginListPanel.stopPlugin(pluginConfig.getPlugin()); + } + }); } - - button.addActionListener(e -> + else { - if (isPluginEnabled) - { - configPanel.stopPlugin(plugin, PluginListItem.this); - } - else - { - configPanel.startPlugin(plugin, PluginListItem.this); - } - - setPluginEnabled(!isPluginEnabled); - updateToggleButton(button); - }); + onOffToggle.setVisible(false); + } } - private IconButton createToggleButton() + boolean isPinned() { - final IconButton button = new IconButton(OFF_SWITCHER); - button.setPreferredSize(new Dimension(25, 0)); - updateToggleButton(button); - attachToggleButtonListener(button); - return button; - } - - void setPluginEnabled(boolean enabled) - { - isPluginEnabled = enabled; - updateToggleButton(toggleButton); - updateToggleButton(configToggleButton); + return pinButton.isSelected(); } void setPinned(boolean pinned) { - isPinned = pinned; - pinButton.setIcon(pinned ? ON_STAR : OFF_STAR); - pinButton.setToolTipText(pinned ? "Unpin plugin" : "Pin plugin"); + pinButton.setSelected(pinned); } - private void updateToggleButton(IconButton button) + void setPluginEnabled(boolean enabled) { - button.setIcon(isPluginEnabled ? ON_SWITCHER : OFF_SWITCHER); - button.setToolTipText(isPluginEnabled ? "Disable plugin" : "Enable plugin"); + onOffToggle.setSelected(enabled); } /** * 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) @@ -297,7 +211,7 @@ class PluginListItem extends JPanel private void openGroupConfigPanel() { - configPanel.openGroupConfigPanel(PluginListItem.this, config, configDescriptor); + pluginListPanel.openConfigurationPanel(pluginConfig); } /** @@ -360,19 +274,4 @@ class PluginListItem extends JPanel } }); } - - /** - * Creates a menu item for linking to a wiki page which, when clicked, opens a link to the plugin's wiki page for - * the passed plugin name. - * - * @param pluginName The name of the plugin which should be linked to - * @return A {@link JMenuItem} which opens the plugin's wiki page URL in the browser when clicked - */ - static JMenuItem wikiLinkMenuItem(final String pluginName) - { - final JMenuItem menuItem = new JMenuItem("Wiki"); - final String sanitizedName = pluginName.replace(' ', '-'); - menuItem.addActionListener(e -> LinkBrowser.browse(String.format(RUNELITE_WIKI_FORMAT, sanitizedName))); - return menuItem; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java new file mode 100644 index 0000000000..abf9ab3bdb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2017, Adam + * 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.Component; +import java.awt.Dimension; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigDescriptor; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.RuneLiteConfig; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.PluginChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.PluginInstantiationException; +import net.runelite.client.plugins.PluginManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.MultiplexingPluginPanel; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.IconTextField; +import net.runelite.client.util.Text; + +@Slf4j +@Singleton +class PluginListPanel extends PluginPanel +{ + private static final String RUNELITE_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value(); + private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins"; + + private final ConfigManager configManager; + private final PluginManager pluginManager; + private final ScheduledExecutorService executorService; + private final Provider configPanelProvider; + private final List fakePlugins = new ArrayList<>(); + + @Getter + private final MultiplexingPluginPanel muxer; + private final IconTextField searchBar; + private final JScrollPane scrollPane; + private final FixedWidthPanel mainPanel; + private List pluginList; + + @Inject + public PluginListPanel( + ConfigManager configManager, + PluginManager pluginManager, + ScheduledExecutorService executorService, + EventBus eventBus, + Provider configPanelProvider) + { + super(false); + + this.configManager = configManager; + this.pluginManager = pluginManager; + this.executorService = executorService; + this.configPanelProvider = configPanelProvider; + + muxer = new MultiplexingPluginPanel(this) + { + @Override + protected void onAdd(PluginPanel p) + { + eventBus.register(p); + } + + @Override + protected void onRemove(PluginPanel p) + { + eventBus.unregister(p); + } + }; + + searchBar = new IconTextField(); + searchBar.setIcon(IconTextField.Icon.SEARCH); + searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30)); + searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + searchBar.getDocument().addDocumentListener(new DocumentListener() + { + @Override + public void insertUpdate(DocumentEvent e) + { + onSearchBarChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) + { + onSearchBarChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) + { + onSearchBarChanged(); + } + }); + + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + JPanel topPanel = new JPanel(); + topPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + topPanel.setLayout(new BorderLayout(0, BORDER_OFFSET)); + topPanel.add(searchBar, BorderLayout.CENTER); + add(topPanel, BorderLayout.NORTH); + + mainPanel = new FixedWidthPanel(); + mainPanel.setBorder(new EmptyBorder(8, 10, 10, 10)); + mainPanel.setLayout(new DynamicGridLayout(0, 1, 0, 5)); + mainPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + + JPanel northPanel = new FixedWidthPanel(); + northPanel.setLayout(new BorderLayout()); + northPanel.add(mainPanel, BorderLayout.NORTH); + + scrollPane = new JScrollPane(northPanel); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + add(scrollPane, BorderLayout.CENTER); + } + + void rebuildPluginList() + { + final List pinnedPlugins = getPinnedPluginNames(); + + // populate pluginList with all non-hidden plugins + pluginList = Stream.concat( + fakePlugins.stream(), + pluginManager.getPlugins().stream() + .filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden()) + .map(plugin -> + { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + Config config = pluginManager.getPluginConfigProxy(plugin); + ConfigDescriptor configDescriptor = config == null ? null : configManager.getConfigDescriptor(config); + + return new PluginConfigurationDescriptor( + descriptor.name(), + descriptor.description(), + descriptor.tags(), + plugin, + config, + configDescriptor); + }) + ).map(desc -> + { + PluginListItem listItem = new PluginListItem(this, desc); + listItem.setPinned(pinnedPlugins.contains(desc.getName())); + return listItem; + }).collect(Collectors.toList()); + + pluginList.sort(Comparator.comparing(p -> p.getPluginConfig().getName())); + mainPanel.removeAll(); + refresh(); + } + + void addFakePlugin(PluginConfigurationDescriptor... descriptor) + { + Collections.addAll(fakePlugins, descriptor); + } + + void refresh() + { + // update enabled / disabled status of all items + pluginList.forEach(listItem -> + { + final Plugin plugin = listItem.getPluginConfig().getPlugin(); + if (plugin != null) + { + listItem.setPluginEnabled(pluginManager.isPluginEnabled(plugin)); + } + }); + + int scrollBarPosition = scrollPane.getVerticalScrollBar().getValue(); + + onSearchBarChanged(); + searchBar.requestFocusInWindow(); + validate(); + + scrollPane.getVerticalScrollBar().setValue(scrollBarPosition); + } + + private void onSearchBarChanged() + { + final String text = searchBar.getText(); + + pluginList.forEach(mainPanel::remove); + + showMatchingPlugins(true, text); + showMatchingPlugins(false, text); + + revalidate(); + } + + private void showMatchingPlugins(boolean pinned, String text) + { + if (text.isEmpty()) + { + pluginList.stream().filter(item -> pinned == item.isPinned()).forEach(mainPanel::add); + return; + } + + final String[] searchTerms = text.toLowerCase().split(" "); + pluginList.forEach(listItem -> + { + if (pinned == listItem.isPinned() && listItem.matchesSearchTerms(searchTerms)) + { + mainPanel.add(listItem); + } + }); + } + + void openConfigurationPanel(String configGroup) + { + for (PluginListItem pluginListItem : pluginList) + { + if (pluginListItem.getPluginConfig().getName().equals(configGroup)) + { + openConfigurationPanel(pluginListItem.getPluginConfig()); + break; + } + } + } + + void openConfigurationPanel(PluginConfigurationDescriptor plugin) + { + ConfigPanel panel = configPanelProvider.get(); + panel.init(plugin); + muxer.pushState(panel); + } + + void startPlugin(Plugin plugin) + { + executorService.submit(() -> + { + pluginManager.setPluginEnabled(plugin, true); + + try + { + pluginManager.startPlugin(plugin); + } + catch (PluginInstantiationException ex) + { + log.warn("Error when starting plugin {}", plugin.getClass().getSimpleName(), ex); + } + }); + } + + void stopPlugin(Plugin plugin) + { + executorService.submit(() -> + { + pluginManager.setPluginEnabled(plugin, false); + + try + { + pluginManager.stopPlugin(plugin); + } + catch (PluginInstantiationException ex) + { + log.warn("Error when stopping plugin {}", plugin.getClass().getSimpleName(), ex); + } + }); + } + + private List getPinnedPluginNames() + { + final String config = configManager.getConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY); + + if (config == null) + { + return Collections.emptyList(); + } + + return Text.fromCSV(config); + } + + void savePinnedPlugins() + { + final String value = pluginList.stream() + .filter(PluginListItem::isPinned) + .map(p -> p.getPluginConfig().getName()) + .collect(Collectors.joining(",")); + + configManager.setConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY, value); + } + + @Subscribe + public void onPluginChanged(PluginChanged event) + { + SwingUtilities.invokeLater(this::refresh); + } + + @Override + public Dimension getPreferredSize() + { + return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height); + } + + @Override + public void onActivate() + { + super.onActivate(); + + if (searchBar.getParent() != null) + { + searchBar.requestFocusInWindow(); + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java new file mode 100644 index 0000000000..0fb22dfbe0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Abex + * 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.Dimension; +import java.awt.image.BufferedImage; +import javax.swing.ImageIcon; +import javax.swing.JToggleButton; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.SwingUtil; + +class PluginToggleButton extends JToggleButton +{ + private static final ImageIcon ON_SWITCHER; + private static final ImageIcon OFF_SWITCHER; + + static + { + BufferedImage onSwitcher = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "switcher_on.png"); + ON_SWITCHER = new ImageIcon(onSwitcher); + OFF_SWITCHER = new ImageIcon(ImageUtil.flipImage( + ImageUtil.grayscaleOffset( + ImageUtil.grayscaleImage(onSwitcher), + 0.61f + ), + true, + false + )); + } + + public PluginToggleButton() + { + super(OFF_SWITCHER); + setSelectedIcon(ON_SWITCHER); + SwingUtil.removeButtonDecorations(this); + setPreferredSize(new Dimension(25, 0)); + SwingUtil.addModalTooltip(this, "Disable plugin", "Enable plugin"); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/MultiplexingPluginPanel.java b/runelite-client/src/main/java/net/runelite/client/ui/MultiplexingPluginPanel.java new file mode 100644 index 0000000000..6454058a4c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/MultiplexingPluginPanel.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Abex + * 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.ui; + +import java.awt.CardLayout; + +public class MultiplexingPluginPanel extends PluginPanel +{ + private final CardLayout layout; + private boolean active = false; + private PluginPanel current; + + public MultiplexingPluginPanel(PluginPanel root) + { + super(false); + + layout = new CardLayout(); + setLayout(layout); + pushState(root); + } + + public void destroy() + { + for (int i = getComponentCount() - 1; i > 0; i--) + { + onRemove((PluginPanel) getComponent(i)); + remove(i); + } + } + + public void pushState(PluginPanel subpanel) + { + int index = -1; + for (int i = getComponentCount() - 1; i >= 0; i--) + { + if (getComponent(i) == subpanel) + { + index = i; + break; + } + } + + if (active) + { + current.onDeactivate(); + subpanel.onActivate(); + } + current = subpanel; + + String name = System.identityHashCode(subpanel) + ""; + + if (index != -1) + { + for (int i = getComponentCount() - 1; i > index; i--) + { + popState(); + } + } + else + { + add(subpanel, name); + onAdd(subpanel); + } + + layout.show(this, name); + revalidate(); + } + + public void popState() + { + int count = getComponentCount(); + if (count <= 1) + { + assert false : "Cannot pop last component"; + return; + } + + PluginPanel subpanel = (PluginPanel) getComponent(count - 2); + if (active) + { + current.onDeactivate(); + subpanel.onActivate(); + current = subpanel; + } + layout.show(this, System.identityHashCode(subpanel) + ""); + onRemove((PluginPanel) getComponent(count - 1)); + remove(count - 1); + revalidate(); + } + + protected void onAdd(PluginPanel p) + { + } + + protected void onRemove(PluginPanel p) + { + } + + @Override + public void onActivate() + { + active = true; + current.onActivate(); + } + + @Override + public void onDeactivate() + { + active = false; + current.onDeactivate(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java b/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java index 73e9a92f8b..f5678f9f8d 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java @@ -36,9 +36,9 @@ import lombok.Getter; public abstract class PluginPanel extends JPanel { public static final int PANEL_WIDTH = 225; - private static final int SCROLLBAR_WIDTH = 17; - private static final int OFFSET = 6; - private static final EmptyBorder BORDER_PADDING = new EmptyBorder(OFFSET, OFFSET, OFFSET, OFFSET); + public static final int SCROLLBAR_WIDTH = 17; + public static final int BORDER_OFFSET = 6; + private static final EmptyBorder BORDER_PADDING = new EmptyBorder(BORDER_OFFSET, BORDER_OFFSET, BORDER_OFFSET, BORDER_OFFSET); private static final Dimension OUTER_PREFERRED_SIZE = new Dimension(PluginPanel.PANEL_WIDTH + SCROLLBAR_WIDTH, 0); @Getter(AccessLevel.PROTECTED) diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java index 39fc738f1f..f4e12c9020 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -29,6 +29,7 @@ import java.awt.Color; import java.awt.Font; import java.awt.Frame; import java.awt.Image; +import java.awt.Insets; import java.awt.SystemTray; import java.awt.TrayIcon; import java.awt.event.MouseAdapter; @@ -41,6 +42,7 @@ import java.util.concurrent.Callable; import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.swing.AbstractButton; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; @@ -277,4 +279,18 @@ public class SwingUtil navigationButton.setOnSelect(button::doClick); return button; } + + public static void removeButtonDecorations(AbstractButton button) + { + button.setBorderPainted(false); + button.setContentAreaFilled(false); + button.setFocusPainted(false); + button.setMargin(new Insets(0, 0, 0, 0)); + button.setOpaque(false); + } + + public static void addModalTooltip(AbstractButton button, String on, String off) + { + button.addItemListener(l -> button.setToolTipText(button.isSelected() ? on : off)); + } }