Merge remote-tracking branch 'origin/runelite' into config-parsing

This commit is contained in:
Owain van Brakel
2021-02-22 23:11:35 +01:00
23 changed files with 517 additions and 178 deletions

View File

@@ -1,22 +1,28 @@
package com.openosrs.client;
import com.google.common.base.Strings;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
public class OpenOSRS
{
public static final File OPENOSRS_DIR = new File(System.getProperty("user.home"), ".openosrs");
public static final File EXTERNALPLUGIN_DIR = new File(OPENOSRS_DIR, "plugins");
public static final String PLUGIN_DEVELOPMENT_PATH = "plugin.development.path";
public static final String SYSTEM_VERSION;
public static final String SYSTEM_API_VERSION;
@Getter(AccessLevel.PACKAGE)
private static final Properties properties = new Properties();
public static String uuid = UUID.randomUUID().toString();
static
{
Properties properties = new Properties();
try
{
properties.load(OpenOSRS.class.getResourceAsStream("/openosrs.properties"));
@@ -25,10 +31,25 @@ public class OpenOSRS
{
e.printStackTrace();
}
SYSTEM_VERSION = properties.getProperty("oprs.version", "0.0.0");
SYSTEM_API_VERSION = properties.getProperty("oprs.api.version");
}
public static String[] getPluginDevelopmentPath()
{
// First check if property supplied as environment variable PLUGIN_DEVELOPMENT_PATHS
String developmentPluginPaths = System.getenv(PLUGIN_DEVELOPMENT_PATH.replace('.', '_').toUpperCase());
if (Strings.isNullOrEmpty(developmentPluginPaths))
{
// Otherwise check the property file
developmentPluginPaths = properties.getProperty(PLUGIN_DEVELOPMENT_PATH);
}
return Strings.isNullOrEmpty(developmentPluginPaths) ? new String[0] : developmentPluginPaths.split(";");
}
public static void preload()
{
}

View File

@@ -32,12 +32,14 @@ public class ConfigDescriptor
{
private final ConfigGroup group;
private final Collection<ConfigSectionDescriptor> sections;
private final Collection<ConfigTitleDescriptor> titles;
private final Collection<ConfigItemDescriptor> items;
public ConfigDescriptor(ConfigGroup group, Collection<ConfigSectionDescriptor> sections, Collection<ConfigItemDescriptor> items)
public ConfigDescriptor(ConfigGroup group, Collection<ConfigSectionDescriptor> sections, Collection<ConfigTitleDescriptor> titles, Collection<ConfigItemDescriptor> items)
{
this.group = group;
this.sections = sections;
this.titles = titles;
this.items = items;
}
}

View File

@@ -49,6 +49,8 @@ public @interface ConfigItem
String section() default "";
String title() default "";
boolean parse() default false;
Class<?> clazz() default void.class;

View File

@@ -56,6 +56,8 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -65,6 +67,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
@@ -647,6 +650,30 @@ public class ConfigManager
.result())
.collect(Collectors.toList());
final List<ConfigTitleDescriptor> titles = Arrays.stream(inter.getDeclaredFields())
.filter(m -> m.isAnnotationPresent(ConfigTitle.class) && m.getType() == String.class)
.map(m ->
{
try
{
return new ConfigTitleDescriptor(
String.valueOf(m.get(inter)),
m.getDeclaredAnnotation(ConfigTitle.class)
);
}
catch (IllegalAccessException e)
{
log.warn("Unable to load title {}::{}", inter.getSimpleName(), m.getName());
return null;
}
})
.filter(Objects::nonNull)
.sorted((a, b) -> ComparisonChain.start()
.compare(a.getTitle().position(), b.getTitle().position())
.compare(a.getTitle().name(), b.getTitle().name())
.result())
.collect(Collectors.toList());
final List<ConfigItemDescriptor> items = Arrays.stream(inter.getMethods())
.filter(m -> m.getParameterCount() == 0 && m.isAnnotationPresent(ConfigItem.class))
.map(m -> new ConfigItemDescriptor(
@@ -662,7 +689,7 @@ public class ConfigManager
.result())
.collect(Collectors.toList());
return new ConfigDescriptor(group, sections, items);
return new ConfigDescriptor(group, sections, titles, items);
}
/**
@@ -680,7 +707,7 @@ public class ConfigManager
return;
}
for (Method method : clazz.getDeclaredMethods())
for (Method method : getAllDeclaredInterfaceMethods(clazz))
{
ConfigItem item = method.getAnnotation(ConfigItem.class);
@@ -868,6 +895,25 @@ public class ConfigManager
return object == null ? null : object.toString();
}
/**
* Does DFS on a class's interfaces to find all of its implemented methods.
*/
private Collection<Method> getAllDeclaredInterfaceMethods(Class<?> clazz)
{
Collection<Method> methods = new HashSet<>();
Stack<Class<?>> interfazes = new Stack<>();
interfazes.push(clazz);
while (!interfazes.isEmpty())
{
Class<?> interfaze = interfazes.pop();
Collections.addAll(methods, interfaze.getDeclaredMethods());
Collections.addAll(interfazes, interfaze.getInterfaces());
}
return methods;
}
@Subscribe(priority = 100)
private void onClientShutdown(ClientShutdown e)
{

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2019, Hydrox6 <ikada@protonmail.ch>
* 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.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ConfigTitle
{
String name();
String description();
int position();
String title() default "";
/*
OpenOSRS Lazy Helpers tm
*/
String keyName() default "";
String section() default "";
boolean hidden() default false;
String unhide() default "";
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2020, Hydrox6 <ikada@protonmail.ch>
* 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.config;
import lombok.Value;
@Value
public class ConfigTitleDescriptor implements ConfigObject
{
private final String key;
private final ConfigTitle title;
@Override
public String key()
{
return key;
}
@Override
public String name()
{
return title.name();
}
@Override
public int position()
{
return title.position();
}
}

View File

@@ -5,6 +5,7 @@ import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -19,6 +20,7 @@ import org.pf4j.CompoundPluginLoader;
import org.pf4j.CompoundPluginRepository;
import org.pf4j.DefaultPluginManager;
import org.pf4j.DependencyResolver;
import org.pf4j.DevelopmentPluginRepository;
import org.pf4j.JarPluginLoader;
import org.pf4j.JarPluginRepository;
import org.pf4j.ManifestPluginDescriptorFinder;
@@ -35,12 +37,9 @@ import org.pf4j.RuntimeMode;
@Slf4j
class OPRSExternalPf4jPluginManager extends DefaultPluginManager
{
private final OPRSExternalPluginManager externalPluginManager;
public OPRSExternalPf4jPluginManager(OPRSExternalPluginManager externalPluginManager)
public OPRSExternalPf4jPluginManager()
{
super(OpenOSRS.EXTERNALPLUGIN_DIR.toPath());
this.externalPluginManager = externalPluginManager;
}
@Override
@@ -68,8 +67,30 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager
{
CompoundPluginRepository compoundPluginRepository = new CompoundPluginRepository();
JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot());
compoundPluginRepository.add(jarPluginRepository);
if (isNotDevelopment())
{
JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot());
compoundPluginRepository.add(jarPluginRepository);
}
if (isDevelopment())
{
for (String developmentPluginPath : OpenOSRS.getPluginDevelopmentPath())
{
DevelopmentPluginRepository developmentPluginRepository = new DevelopmentPluginRepository(Paths.get(developmentPluginPath))
{
@Override
public boolean deletePluginPath(Path pluginPath)
{
// Do nothing, because we'd be deleting our sources!
return filter.accept(pluginPath.toFile());
}
};
developmentPluginRepository.setFilter(new OPRSExternalPluginFileFilter());
compoundPluginRepository.add(developmentPluginRepository);
}
}
return compoundPluginRepository;
}
@@ -121,6 +142,12 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager
{
if (!(e instanceof PluginAlreadyLoadedException))
{
if (!OPRSExternalPluginManager.isDevelopmentMode())
{
String plugin = pluginPath.toString().substring(pluginsRoots.get(0).toString().length() + 1);
duplicatePlugins.add(plugin);
}
log.error("Could not load plugin {}", pluginPath, e);
}
}
@@ -220,7 +247,7 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager
@Override
public RuntimeMode getRuntimeMode()
{
return RuntimeMode.DEPLOYMENT;
return OPRSExternalPluginManager.isDevelopmentMode() ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
}
@Override

View File

@@ -33,6 +33,7 @@ import com.google.inject.CreationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.openosrs.client.OpenOSRS;
import static com.openosrs.client.OpenOSRS.EXTERNALPLUGIN_DIR;
import static com.openosrs.client.OpenOSRS.SYSTEM_API_VERSION;
import com.openosrs.client.config.OpenOSRSConfig;
@@ -108,6 +109,8 @@ public class OPRSExternalPluginManager
private final ConfigManager configManager;
private final Map<String, String> pluginsMap = new HashMap<>();
@Getter(AccessLevel.PUBLIC)
private static final boolean developmentMode = OpenOSRS.getPluginDevelopmentPath().length > 0;
@Getter(AccessLevel.PUBLIC)
private final Map<String, Map<String, String>> pluginsInfoMap = new HashMap<>();
private final Groups groups;
@Getter(AccessLevel.PUBLIC)
@@ -143,7 +146,7 @@ public class OPRSExternalPluginManager
private void initPluginManager()
{
externalPluginManager = new OPRSExternalPf4jPluginManager(this);
externalPluginManager = new OPRSExternalPf4jPluginManager();
externalPluginManager.setSystemVersion(SYSTEM_API_VERSION);
}
@@ -826,29 +829,39 @@ public class OPRSExternalPluginManager
try
{
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
// Null version returns the last release version of this plugin for given system version
if (latest == null)
if (!developmentMode)
{
try
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
// Null version returns the last release version of this plugin for given system version
if (latest == null)
{
SwingUtil.syncExec(() ->
JOptionPane.showMessageDialog(ClientUI.getFrame(),
pluginId + " is outdated and cannot be installed",
"Installation error",
JOptionPane.ERROR_MESSAGE));
}
catch (InvocationTargetException | InterruptedException ignored)
{
return false;
try
{
SwingUtil.syncExec(() ->
JOptionPane.showMessageDialog(ClientUI.getFrame(),
pluginId + " is outdated and cannot be installed",
"Installation error",
JOptionPane.ERROR_MESSAGE));
}
catch (InvocationTargetException | InterruptedException ignored)
{
return false;
}
return true;
}
return true;
updateManager.installPlugin(pluginId, null);
scanAndInstantiate(loadPlugin(pluginId), true, true);
}
else
{
// In development mode our plugin will already be present in a repository, so we can just load it
externalPluginManager.loadPlugins();
externalPluginManager.startPlugin(pluginId);
}
updateManager.installPlugin(pluginId, null);
scanAndInstantiate(loadPlugin(pluginId), true, true);
ExternalPluginsChanged event = new ExternalPluginsChanged(null);
eventBus.post(event);
groups.broadcastSring("STARTEXTERNAL;" + pluginId);
@@ -908,6 +921,11 @@ public class OPRSExternalPluginManager
log.info("Not updating external plugins since there is more than 1 client open");
return;
}
else if (developmentMode)
{
log.info("Not updating because we're running in developer mode");
return;
}
OpenOSRSSplashScreen.stage(.59, "Updating external plugins");

View File

@@ -27,10 +27,15 @@ package net.runelite.client.plugins.config;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
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.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
@@ -56,6 +61,7 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
@@ -79,6 +85,8 @@ import net.runelite.client.config.ConfigManager;
import net.runelite.client.config.ConfigObject;
import net.runelite.client.config.ConfigSection;
import net.runelite.client.config.ConfigSectionDescriptor;
import net.runelite.client.config.ConfigTitle;
import net.runelite.client.config.ConfigTitleDescriptor;
import net.runelite.client.config.Keybind;
import net.runelite.client.config.ModifierlessKeybind;
import net.runelite.client.config.Range;
@@ -89,6 +97,7 @@ import net.runelite.client.events.ExternalPluginsChanged;
import net.runelite.client.events.PluginChanged;
import net.runelite.client.externalplugins.ExternalPluginManager;
import net.runelite.client.externalplugins.ExternalPluginManifest;
import net.runelite.client.plugins.OPRSExternalPluginManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.ui.ColorScheme;
@@ -102,6 +111,7 @@ 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;
@@ -134,6 +144,9 @@ class ConfigPanel extends PluginPanel
@Inject
private ExternalPluginManager externalPluginManager;
@Inject
private OPRSExternalPluginManager oprsExternalPluginManager;
@Inject
private ColorPickerManager colorPickerManager;
@@ -259,7 +272,55 @@ class ConfigPanel extends PluginPanel
ConfigDescriptor cd = pluginConfig.getConfigDescriptor();
Map<String, Map<String, String>> pluginsInfoMap = oprsExternalPluginManager.getPluginsInfoMap();
if (pluginConfig.getPlugin() != null && pluginsInfoMap.containsKey(pluginConfig.getPlugin().getClass().getSimpleName()))
{
JPanel infoPanel = new JPanel();
infoPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
infoPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
infoPanel.setLayout(new GridLayout(0, 1));
final Font smallFont = FontManager.getRunescapeSmallFont();
Map<String, String> pluginInfo = pluginsInfoMap.get(pluginConfig.getPlugin().getClass().getSimpleName());
JLabel idLabel = new JLabel(htmlLabel("id", pluginInfo.get("id")));
idLabel.setFont(smallFont);
infoPanel.add(idLabel);
JLabel versionLabel = new JLabel(htmlLabel("version", pluginInfo.get("version")));
versionLabel.setFont(smallFont);
infoPanel.add(versionLabel);
JLabel providerLabel = new JLabel(htmlLabel("provider", pluginInfo.get("provider")));
providerLabel.setFont(smallFont);
infoPanel.add(providerLabel);
JButton button = new JButton("Support");
button.addActionListener(e -> LinkBrowser.browse(pluginInfo.get("support")));
JSeparator separator = new JSeparator()
{
@Override
protected void paintComponent(Graphics g)
{
int width = this.getSize().width;
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
g2.setColor(ColorScheme.BRAND_BLUE);
g2.drawLine(0, 0, width, 0);
}
};
mainPanel.add(infoPanel);
mainPanel.add(button);
mainPanel.add(separator);
}
final Map<String, JPanel> sectionWidgets = new HashMap<>();
final Map<String, JPanel> titleWidgets = new HashMap<>();
final Map<ConfigObject, JPanel> topLevelPanels = new TreeMap<>((a, b) ->
ComparisonChain.start()
.compare(a.position(), b.position())
@@ -327,6 +388,53 @@ class ConfigPanel extends PluginPanel
topLevelPanels.put(csd, section);
}
for (ConfigTitleDescriptor ctd : cd.getTitles())
{
ConfigTitle ct = ctd.getTitle();
final JPanel title = new JPanel();
title.setLayout(new BoxLayout(title, BoxLayout.Y_AXIS));
title.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
final JPanel sectionHeader = new JPanel();
sectionHeader.setLayout(new BorderLayout());
sectionHeader.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
title.add(sectionHeader, BorderLayout.NORTH);
String name = ct.name();
final JLabel sectionName = new JLabel(name);
sectionName.setForeground(ColorScheme.BRAND_ORANGE);
sectionName.setFont(FontManager.getRunescapeBoldFont());
sectionName.setToolTipText("<html>" + name + ":<br>" + ct.description() + "</html>");
sectionName.setBorder(new EmptyBorder(0, 0, 3, 1));
sectionHeader.add(sectionName, BorderLayout.CENTER);
final JPanel sectionContents = new JPanel();
sectionContents.setLayout(new DynamicGridLayout(0, 1, 0, 5));
sectionContents.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
sectionContents.setBorder(new EmptyBorder(0, 5, 0, 0));
title.add(sectionContents, BorderLayout.SOUTH);
titleWidgets.put(ctd.getKey(), sectionContents);
// Allow for sub-sections
JPanel section = sectionWidgets.get(ct.section());
JPanel titleSection = titleWidgets.get(ct.title());
if (section != null)
{
section.add(title);
}
else if (titleSection != null)
{
titleSection.add(title);
}
else
{
topLevelPanels.put(ctd, title);
}
}
for (ConfigItemDescriptor cid : cd.getItems())
{
if (cid.getItem().hidden())
@@ -595,13 +703,19 @@ class ConfigPanel extends PluginPanel
}
JPanel section = sectionWidgets.get(cid.getItem().section());
if (section == null)
JPanel title = titleWidgets.get(cid.getItem().title());
if (section != null)
{
topLevelPanels.put(cid, item);
section.add(item);
}
else if (title != null)
{
title.add(item);
}
else
{
section.add(item);
topLevelPanels.put(cid, item);
}
}
@@ -769,4 +883,9 @@ class ConfigPanel extends PluginPanel
});
return menuItem;
}
private static String htmlLabel(String key, String value)
{
return "<html><body style = 'color:#a5a5a5'>" + key + ": <span style = 'color:white'>" + value + "</span></body></html>";
}
}

View File

@@ -37,17 +37,23 @@ import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.externalplugins.ExternalPluginManifest;
import net.runelite.client.plugins.OPRSExternalPluginManager;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.util.ImageUtil;
@@ -57,6 +63,8 @@ class PluginListItem extends JPanel implements SearchablePlugin
{
private static final ImageIcon CONFIG_ICON;
private static final ImageIcon CONFIG_ICON_HOVER;
private static final ImageIcon REFRESH_ICON;
private static final ImageIcon REFRESH_ICON_HOVER;
private static final ImageIcon ON_STAR;
private static final ImageIcon OFF_STAR;
@@ -74,10 +82,13 @@ class PluginListItem extends JPanel implements SearchablePlugin
static
{
BufferedImage configIcon = ImageUtil.loadImageResource(ConfigPanel.class, "config_edit_icon.png");
BufferedImage refreshIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "refresh.png");
BufferedImage onStar = ImageUtil.loadImageResource(ConfigPanel.class, "star_on.png");
CONFIG_ICON = new ImageIcon(configIcon);
REFRESH_ICON = new ImageIcon(refreshIcon);
ON_STAR = new ImageIcon(onStar);
CONFIG_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(configIcon, -100));
REFRESH_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(refreshIcon, -100));
BufferedImage offStar = ImageUtil.luminanceScale(
ImageUtil.grayscaleImage(onStar),
@@ -86,7 +97,7 @@ class PluginListItem extends JPanel implements SearchablePlugin
OFF_STAR = new ImageIcon(offStar);
}
PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig)
PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig, OPRSExternalPluginManager oprsExternalPluginManager)
{
this.pluginListPanel = pluginListPanel;
this.pluginConfig = pluginConfig;
@@ -133,6 +144,60 @@ class PluginListItem extends JPanel implements SearchablePlugin
buttonPanel.setLayout(new GridLayout(1, 2));
add(buttonPanel, BorderLayout.LINE_END);
Map<String, Map<String, String>> pluginsInfoMap = oprsExternalPluginManager.getPluginsInfoMap();
if ((OPRSExternalPluginManager.isDevelopmentMode() || RuneLiteProperties.getLauncherVersion() == null) && pluginConfig.getPlugin() != null && pluginsInfoMap.containsKey(pluginConfig.getPlugin().getClass().getSimpleName()))
{
JButton hotSwapButton = new JButton(REFRESH_ICON);
hotSwapButton.setRolloverIcon(REFRESH_ICON_HOVER);
SwingUtil.removeButtonDecorations(hotSwapButton);
hotSwapButton.setPreferredSize(new Dimension(25, 0));
hotSwapButton.setVisible(false);
buttonPanel.add(hotSwapButton);
hotSwapButton.addActionListener(e ->
{
Map<String, String> pluginInfo = pluginsInfoMap.get(pluginConfig.getPlugin().getClass().getSimpleName());
String pluginId = pluginInfo.get("id");
hotSwapButton.setIcon(REFRESH_ICON);
new SwingWorker<>()
{
@Override
protected Boolean doInBackground()
{
return oprsExternalPluginManager.uninstall(pluginId);
}
@Override
protected void done()
{
// In development mode our plugins will be loaded directly from sources, so we don't need to prompt
if (!OPRSExternalPluginManager.isDevelopmentMode())
{
JOptionPane.showMessageDialog(ClientUI.getFrame(),
pluginId + " is unloaded, put the new jar file in the externalmanager folder and click `ok`",
"Hotswap " + pluginId,
JOptionPane.INFORMATION_MESSAGE);
}
new SwingWorker<>()
{
@Override
protected Boolean doInBackground()
{
return oprsExternalPluginManager.reloadStart(pluginId);
}
}.execute();
}
}.execute();
});
hotSwapButton.setVisible(true);
hotSwapButton.setToolTipText("Hotswap plugin");
}
JMenuItem configMenuItem = null;
if (pluginConfig.hasConfigurables())
{

View File

@@ -57,6 +57,7 @@ import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ExternalPluginsChanged;
import net.runelite.client.events.PluginChanged;
import net.runelite.client.externalplugins.ExternalPluginManager;
import net.runelite.client.plugins.OPRSExternalPluginManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginInstantiationException;
@@ -93,6 +94,7 @@ class PluginListPanel extends PluginPanel
@Getter
private final ExternalPluginManager externalPluginManager;
private final OPRSExternalPluginManager oprsExternalPluginManager;
@Getter
private final MultiplexingPluginPanel muxer;
@@ -107,6 +109,7 @@ class PluginListPanel extends PluginPanel
ConfigManager configManager,
PluginManager pluginManager,
ExternalPluginManager externalPluginManager,
OPRSExternalPluginManager oprsExternalPluginManager,
EventBus eventBus,
Provider<ConfigPanel> configPanelProvider,
Provider<PluginHubPanel> pluginHubPanelProvider)
@@ -116,6 +119,7 @@ class PluginListPanel extends PluginPanel
this.configManager = configManager;
this.pluginManager = pluginManager;
this.externalPluginManager = externalPluginManager;
this.oprsExternalPluginManager = oprsExternalPluginManager;
this.configPanelProvider = configPanelProvider;
muxer = new MultiplexingPluginPanel(this)
@@ -215,7 +219,7 @@ class PluginListPanel extends PluginPanel
)
.map(desc ->
{
PluginListItem listItem = new PluginListItem(this, desc);
PluginListItem listItem = new PluginListItem(this, desc, oprsExternalPluginManager);
listItem.setPinned(pinnedPlugins.contains(desc.getName()));
return listItem;
})