Merge branch 'master' into externalserror1
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
package net.runelite.client;
|
||||
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.UUID;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -30,8 +30,8 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.SentryClient;
|
||||
import java.io.File;
|
||||
@@ -39,14 +39,18 @@ import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import joptsimple.ArgumentAcceptingOptionSpec;
|
||||
import joptsimple.OptionException;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.ValueConversionException;
|
||||
import joptsimple.ValueConverter;
|
||||
import joptsimple.util.EnumConverter;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -85,6 +89,7 @@ import net.runelite.client.ui.overlay.arrow.ArrowWorldOverlay;
|
||||
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
|
||||
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
|
||||
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
|
||||
import net.runelite.client.util.AppLock;
|
||||
import net.runelite.client.util.WorldUtil;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
import net.runelite.http.api.worlds.World;
|
||||
@@ -105,6 +110,7 @@ public class RuneLite
|
||||
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
|
||||
public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs");
|
||||
public static final File PLUGINS_DIR = new File(RUNELITE_DIR, "plugins");
|
||||
public static final File DEFAULT_CONFIG_FILE = new File(RUNELITE_DIR, "runeliteplus.properties");
|
||||
public static final Locale SYSTEM_LOCALE = Locale.getDefault();
|
||||
public static boolean allowPrivateServer = false;
|
||||
|
||||
@@ -201,6 +207,9 @@ public class RuneLite
|
||||
@Inject
|
||||
private Scheduler scheduler;
|
||||
|
||||
@Inject
|
||||
private AppLock appLock;
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
@@ -215,6 +224,11 @@ public class RuneLite
|
||||
final ArgumentAcceptingOptionSpec<Integer> worldInfo = parser
|
||||
.accepts("world")
|
||||
.withRequiredArg().ofType(Integer.class);
|
||||
final ArgumentAcceptingOptionSpec<File> configfile = parser.accepts("config", "Use a specified config file")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new ConfigFileConverter())
|
||||
.defaultsTo(DEFAULT_CONFIG_FILE);
|
||||
|
||||
final ArgumentAcceptingOptionSpec<ClientUpdateCheckMode> updateMode = parser
|
||||
.accepts("rs", "Select client type")
|
||||
.withRequiredArg()
|
||||
@@ -230,7 +244,18 @@ public class RuneLite
|
||||
});
|
||||
|
||||
parser.accepts("help", "Show this text").forHelp();
|
||||
OptionSet options = parser.parse(args);
|
||||
|
||||
OptionSet options = parser.parse("");
|
||||
|
||||
try
|
||||
{
|
||||
options = parser.parse(args);
|
||||
}
|
||||
catch (OptionException e)
|
||||
{
|
||||
log.warn("Error parsing launch args: {}", e.getMessage());
|
||||
log.warn("Proceeding with no arguments.");
|
||||
}
|
||||
|
||||
if (options.has("help"))
|
||||
{
|
||||
@@ -297,18 +322,6 @@ public class RuneLite
|
||||
RuneLiteSplashScreen.init();
|
||||
}
|
||||
|
||||
final boolean developerMode = options.has("developer-mode");
|
||||
|
||||
if (developerMode)
|
||||
{
|
||||
boolean assertions = false;
|
||||
assert assertions = true;
|
||||
if (!assertions)
|
||||
{
|
||||
log.warn("Developers should enable assertions; Add `-ea` to your JVM arguments`");
|
||||
}
|
||||
}
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) ->
|
||||
{
|
||||
log.error("Uncaught exception:", throwable);
|
||||
@@ -327,7 +340,7 @@ public class RuneLite
|
||||
|
||||
injector = Guice.createInjector(new RuneLiteModule(
|
||||
clientLoader,
|
||||
true));
|
||||
options.valueOf(configfile)));
|
||||
|
||||
injector.getInstance(RuneLite.class).start();
|
||||
final long end = System.currentTimeMillis();
|
||||
@@ -368,9 +381,11 @@ public class RuneLite
|
||||
externalPluginManager.startExternalUpdateManager();
|
||||
externalPluginManager.startExternalPluginManager();
|
||||
|
||||
|
||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||
externalPluginManager.update();
|
||||
if (appLock.lock(this.getClass().getName()))
|
||||
{
|
||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||
externalPluginManager.update();
|
||||
}
|
||||
|
||||
// Load the plugins, but does not start them yet.
|
||||
// This will initialize configuration
|
||||
@@ -387,7 +402,7 @@ public class RuneLite
|
||||
pluginManager.loadDefaultPluginConfiguration();
|
||||
|
||||
// Start client session
|
||||
RuneLiteSplashScreen.stage(.80, "Starting core interface");
|
||||
RuneLiteSplashScreen.stage(.77, "Starting core interface");
|
||||
clientSessionManager.start();
|
||||
|
||||
//Set the world if specified via CLI args - will not work until clientUI.init is called
|
||||
@@ -395,7 +410,7 @@ public class RuneLite
|
||||
worldArg.ifPresent(this::setWorld);
|
||||
|
||||
// Initialize UI
|
||||
RuneLiteSplashScreen.stage(.77, "Initialize UI");
|
||||
RuneLiteSplashScreen.stage(.80, "Initialize UI");
|
||||
clientUI.init(this);
|
||||
|
||||
// Initialize Discord service
|
||||
@@ -487,8 +502,47 @@ public class RuneLite
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
configManager.sendConfig();
|
||||
clientSessionManager.shutdown();
|
||||
discordService.close();
|
||||
appLock.release();
|
||||
}
|
||||
|
||||
private static class ConfigFileConverter implements ValueConverter<File>
|
||||
{
|
||||
@Override
|
||||
public File convert(String fileName)
|
||||
{
|
||||
final File file;
|
||||
|
||||
if (Paths.get(fileName).isAbsolute()
|
||||
|| fileName.startsWith("./")
|
||||
|| fileName.startsWith(".\\"))
|
||||
{
|
||||
file = new File(fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = new File(RuneLite.RUNELITE_DIR, fileName);
|
||||
}
|
||||
|
||||
if (file.exists() && (!file.isFile() || !file.canWrite()))
|
||||
{
|
||||
throw new ValueConversionException(String.format("File %s is not accessible", file.getAbsolutePath()));
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends File> valueType()
|
||||
{
|
||||
return File.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String valuePattern()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import net.runelite.client.callback.Hooks;
|
||||
import net.runelite.client.chat.ChatMessageManager;
|
||||
import net.runelite.client.config.ChatColorConfig;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.LauncherConfig;
|
||||
import net.runelite.client.config.OpenOSRSConfig;
|
||||
import net.runelite.client.config.RuneLiteConfig;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
@@ -61,18 +62,18 @@ public class RuneLiteModule extends AbstractModule
|
||||
private static final int MAX_OKHTTP_CACHE_SIZE = 20 * 1024 * 1024; // 20mb
|
||||
|
||||
private final Supplier<Applet> clientLoader;
|
||||
private final boolean developerMode;
|
||||
private final File config;
|
||||
|
||||
public RuneLiteModule(final Supplier<Applet> clientLoader, boolean developerMode)
|
||||
public RuneLiteModule(final Supplier<Applet> clientLoader, File config)
|
||||
{
|
||||
this.clientLoader = clientLoader;
|
||||
this.developerMode = developerMode;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure()
|
||||
{
|
||||
bindConstant().annotatedWith(Names.named("developerMode")).to(developerMode);
|
||||
bind(File.class).annotatedWith(Names.named("config")).toInstance(config);
|
||||
bind(ScheduledExecutorService.class).toInstance(new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor()));
|
||||
bind(OkHttpClient.class).toInstance(RuneLiteAPI.CLIENT.newBuilder()
|
||||
.cache(new Cache(new File(RuneLite.CACHE_DIR, "okhttp"), MAX_OKHTTP_CACHE_SIZE))
|
||||
@@ -132,4 +133,11 @@ public class RuneLiteModule extends AbstractModule
|
||||
{
|
||||
return configManager.getConfig(ChatColorConfig.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LauncherConfig provideLauncherConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(LauncherConfig.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,8 +136,7 @@ public class RuneLiteProperties
|
||||
@Nullable
|
||||
public static String getLauncherVersion()
|
||||
{
|
||||
String launcherVersion = properties.getProperty(LAUNCHER_VERSION_PROPERTY);
|
||||
return launcherVersion.equals("-1") ? null : launcherVersion;
|
||||
return System.getProperty(LAUNCHER_VERSION_PROPERTY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
*/
|
||||
package net.runelite.client;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import net.runelite.http.api.RuneLiteAPI;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
package net.runelite.client.account;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
package net.runelite.client.callback;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -59,6 +59,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
@@ -75,20 +76,20 @@ import org.apache.commons.lang3.StringUtils;
|
||||
@Slf4j
|
||||
public class ConfigManager
|
||||
{
|
||||
private static final String SETTINGS_FILE_NAME = "runeliteplus.properties";
|
||||
private static final String STANDARD_SETTINGS_FILE_NAME = "settings.properties";
|
||||
private static final File SETTINGS_FILE = new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME);
|
||||
private static final File STANDARD_SETTINGS_FILE = new File(RuneLite.RUNELITE_DIR, STANDARD_SETTINGS_FILE_NAME);
|
||||
private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this);
|
||||
private final Properties properties = new Properties();
|
||||
private final Map<String, String> pendingChanges = new HashMap<>();
|
||||
private final File settingsFileInput;
|
||||
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
public ConfigManager(ScheduledExecutorService scheduledExecutorService)
|
||||
public ConfigManager(@Named("config") File config, ScheduledExecutorService scheduledExecutorService)
|
||||
{
|
||||
this.settingsFileInput = config;
|
||||
scheduledExecutorService.scheduleWithFixedDelay(this::sendConfig, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@@ -414,7 +415,7 @@ public class ConfigManager
|
||||
handler.invalidate();
|
||||
properties.clear();
|
||||
|
||||
try (FileInputStream in = new FileInputStream(SETTINGS_FILE))
|
||||
try (FileInputStream in = new FileInputStream(settingsFileInput))
|
||||
{
|
||||
properties.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
}
|
||||
@@ -460,9 +461,9 @@ public class ConfigManager
|
||||
|
||||
private void saveToFile() throws IOException
|
||||
{
|
||||
ConfigManager.SETTINGS_FILE.getParentFile().mkdirs();
|
||||
settingsFileInput.getParentFile().mkdirs();
|
||||
|
||||
File tempFile = new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME + ".tmp");
|
||||
File tempFile = new File(RuneLite.RUNELITE_DIR, RuneLite.DEFAULT_CONFIG_FILE.getName() + ".tmp");
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(tempFile))
|
||||
{
|
||||
@@ -473,12 +474,12 @@ public class ConfigManager
|
||||
|
||||
try
|
||||
{
|
||||
Files.move(tempFile.toPath(), ConfigManager.SETTINGS_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
Files.move(tempFile.toPath(), settingsFileInput.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
}
|
||||
catch (AtomicMoveNotSupportedException ex)
|
||||
{
|
||||
log.debug("atomic move not supported", ex);
|
||||
Files.move(tempFile.toPath(), ConfigManager.SETTINGS_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(tempFile.toPath(), settingsFileInput.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
|
||||
* 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.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@ConfigGroup("openosrs")
|
||||
public interface LauncherConfig extends Config
|
||||
{
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
@AllArgsConstructor
|
||||
enum BootstrapMode
|
||||
{
|
||||
STABLE("Stable"),
|
||||
NIGHTLY("Nightly");
|
||||
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigTitleSection(
|
||||
keyName = "launcherTitle",
|
||||
name = "Launcher",
|
||||
description = "",
|
||||
position = -1
|
||||
)
|
||||
default Title launcherTitle()
|
||||
{
|
||||
return new Title();
|
||||
}
|
||||
|
||||
@ConfigTitleSection(
|
||||
keyName = "updateChannelTitle",
|
||||
name = "Update channel",
|
||||
description = "",
|
||||
position = 1,
|
||||
titleSection = "launcherTitle"
|
||||
)
|
||||
default Title updateChannelTitle()
|
||||
{
|
||||
return new Title();
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 2,
|
||||
keyName = "askMode",
|
||||
name = "Prompt for update channel",
|
||||
description = "Ask for nightly or stable every startup",
|
||||
titleSection = "updateChannelTitle"
|
||||
)
|
||||
default boolean askMode()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "bootstrapMode",
|
||||
name = "Update channel",
|
||||
description = "Select the update channel",
|
||||
titleSection = "updateChannelTitle",
|
||||
position = 3,
|
||||
hide = "askMode"
|
||||
)
|
||||
default BootstrapMode bootstrapMode()
|
||||
{
|
||||
return BootstrapMode.STABLE;
|
||||
}
|
||||
|
||||
@ConfigTitleSection(
|
||||
keyName = "miscLauncherTitle",
|
||||
name = "Miscellaneous",
|
||||
description = "",
|
||||
position = 4,
|
||||
titleSection = "launcherTitle"
|
||||
)
|
||||
default Title miscLauncherTitle()
|
||||
{
|
||||
return new Title();
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 5,
|
||||
keyName = "disableHw",
|
||||
name = "Disable hardware acceleration",
|
||||
description = "Enable this if you have graphical issues",
|
||||
titleSection = "miscLauncherTitle",
|
||||
warning = "Toggling this setting requires a restart of the client"
|
||||
)
|
||||
default boolean disableHw()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigTitleSection(
|
||||
keyName = "advancedTitle",
|
||||
name = "Advanced",
|
||||
description = "",
|
||||
position = 6,
|
||||
titleSection = "launcherTitle"
|
||||
)
|
||||
default Title advancedTitle()
|
||||
{
|
||||
return new Title();
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 7,
|
||||
keyName = "noJvm",
|
||||
name = "Use system java (caution!)",
|
||||
description = "Enable this if you want to make use of the system java version instead of the launcher bundled version",
|
||||
titleSection = "advancedTitle",
|
||||
warning = "Toggling this setting requires a restart of the client"
|
||||
)
|
||||
default boolean noJvm()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 8,
|
||||
keyName = "useProxy",
|
||||
name = "Use SOCKS5 proxy",
|
||||
description = "Enable the client to use a proxy",
|
||||
titleSection = "advancedTitle",
|
||||
warning = "Toggling this setting requires a restart of the client"
|
||||
)
|
||||
default boolean useProxy()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "proxyDetails",
|
||||
name = "Proxy details",
|
||||
description = "The format for this field is ip:port or ip:port:user:pass",
|
||||
titleSection = "advancedTitle",
|
||||
position = 9,
|
||||
hidden = true,
|
||||
unhide = "useProxy"
|
||||
)
|
||||
default String proxyDetails()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import lombok.Value;
|
||||
/**
|
||||
* Represents Discord Rich Presence RPC data
|
||||
*/
|
||||
@Builder
|
||||
@Builder(toBuilder = true)
|
||||
@Value
|
||||
public class DiscordPresence
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.runelite.client.eventbus;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package net.runelite.client.eventbus;
|
||||
|
||||
import com.jakewharton.rxrelay2.PublishRelay;
|
||||
import com.jakewharton.rxrelay2.Relay;
|
||||
import io.reactivex.ObservableTransformer;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.annotations.Nullable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import com.jakewharton.rxrelay3.PublishRelay;
|
||||
import com.jakewharton.rxrelay3.Relay;
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.core.ObservableTransformer;
|
||||
import io.reactivex.rxjava3.core.Scheduler;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import io.sentry.Sentry;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.runelite.client.eventbus;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import net.runelite.api.events.Event;
|
||||
|
||||
public interface EventBusInterface
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package net.runelite.client.eventbus;
|
||||
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.annotations.Nullable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.function.Supplier;
|
||||
import io.reactivex.rxjava3.core.Scheduler;
|
||||
import io.reactivex.rxjava3.functions.Supplier;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@@ -22,6 +22,14 @@ public enum EventScheduler
|
||||
@Nullable
|
||||
public Scheduler get()
|
||||
{
|
||||
return scheduler.get();
|
||||
try
|
||||
{
|
||||
return scheduler.get();
|
||||
}
|
||||
catch (Throwable ignored)
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.runelite.client.eventbus;
|
||||
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
|
||||
@@ -28,8 +28,8 @@ import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -27,8 +27,8 @@ package net.runelite.client.game;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -28,11 +28,14 @@ package net.runelite.client.game.chatbox;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.inject.Inject;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.ItemDefinition;
|
||||
import net.runelite.api.widgets.ItemQuantityMode;
|
||||
@@ -66,6 +69,14 @@ public class ChatboxItemSearch extends ChatboxTextInput
|
||||
@Getter
|
||||
private Consumer<Integer> onItemSelected;
|
||||
|
||||
@Value
|
||||
private static class ItemIcon
|
||||
{
|
||||
private final int modelId;
|
||||
private final short[] colorsToReplace;
|
||||
private final short[] texturesToReplace;
|
||||
}
|
||||
|
||||
@Inject
|
||||
private ChatboxItemSearch(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread,
|
||||
ItemManager itemManager, Client client)
|
||||
@@ -287,15 +298,26 @@ public class ChatboxItemSearch extends ChatboxTextInput
|
||||
return;
|
||||
}
|
||||
|
||||
Set<ItemIcon> itemIcons = new HashSet<>();
|
||||
for (int i = 0; i < client.getItemCount() && results.size() < MAX_RESULTS; i++)
|
||||
{
|
||||
ItemDefinition itemComposition = itemManager.getItemDefinition(itemManager.canonicalize(i));
|
||||
String name = itemComposition.getName().toLowerCase();
|
||||
|
||||
// The client assigns "null" to item names of items it doesn't know about
|
||||
if (!name.equals("null") && name.contains(search))
|
||||
// and the item might already be in the results from canonicalize
|
||||
if (!name.equals("null") && name.contains(search) && !results.containsKey(itemComposition.getId()))
|
||||
{
|
||||
// This may already be in the map due to canonicalize mapping the item to something we've already seen
|
||||
results.putIfAbsent(itemComposition.getId(), itemComposition);
|
||||
// Check if the results already contain the same item image
|
||||
ItemIcon itemIcon = new ItemIcon(itemComposition.getInventoryModel(),
|
||||
itemComposition.getColorToReplaceWith(), itemComposition.getTextureToReplaceWith());
|
||||
if (itemIcons.contains(itemIcon))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemIcons.add(itemIcon);
|
||||
results.put(itemComposition.getId(), itemComposition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -49,7 +50,9 @@ import net.runelite.client.config.OpenOSRSConfig;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.events.ExternalPluginChanged;
|
||||
import net.runelite.client.events.ExternalRepositoryChanged;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.RuneLiteSplashScreen;
|
||||
import net.runelite.client.util.MiscUtils;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
import org.pf4j.DefaultPluginManager;
|
||||
import org.pf4j.DependencyResolver;
|
||||
@@ -77,7 +80,7 @@ class ExternalPluginManager
|
||||
{
|
||||
public static ArrayList<ClassLoader> pluginClassLoaders = new ArrayList<>();
|
||||
private final PluginManager runelitePluginManager;
|
||||
private final org.pf4j.PluginManager externalPluginManager;
|
||||
private org.pf4j.PluginManager externalPluginManager;
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private final List<UpdateRepository> repositories = new ArrayList<>();
|
||||
private final OpenOSRSConfig openOSRSConfig;
|
||||
@@ -85,7 +88,6 @@ class ExternalPluginManager
|
||||
private final ConfigManager configManager;
|
||||
private final List<Plugin> plugins = new CopyOnWriteArrayList<>();
|
||||
private final Map<String, String> pluginsMap = new HashMap<>();
|
||||
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private UpdateManager updateManager;
|
||||
|
||||
@@ -104,9 +106,16 @@ class ExternalPluginManager
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
EXTERNALPLUGIN_DIR.mkdirs();
|
||||
|
||||
initPluginManager();
|
||||
}
|
||||
|
||||
private void initPluginManager()
|
||||
{
|
||||
boolean debug = RuneLiteProperties.getLauncherVersion() == null && RuneLiteProperties.getPluginPath() != null;
|
||||
|
||||
this.externalPluginManager = new DefaultPluginManager(debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release") : EXTERNALPLUGIN_DIR.toPath())
|
||||
this.externalPluginManager = new DefaultPluginManager(
|
||||
debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release")
|
||||
: EXTERNALPLUGIN_DIR.toPath())
|
||||
{
|
||||
@Override
|
||||
protected PluginDescriptorFinder createPluginDescriptorFinder()
|
||||
@@ -117,7 +126,27 @@ class ExternalPluginManager
|
||||
@Override
|
||||
protected PluginRepository createPluginRepository()
|
||||
{
|
||||
return new JarPluginRepository(getPluginsRoot());
|
||||
return new JarPluginRepository(getPluginsRoot())
|
||||
{
|
||||
@Override
|
||||
public List<Path> getPluginPaths()
|
||||
{
|
||||
File[] files = pluginsRoot.toFile().listFiles(filter);
|
||||
|
||||
if ((files == null) || files.length == 0)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Path> paths = new ArrayList<>(files.length);
|
||||
for (File file : files)
|
||||
{
|
||||
paths.add(file.toPath());
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,22 +213,41 @@ class ExternalPluginManager
|
||||
this.externalPluginManager.setSystemVersion(SYSTEM_VERSION);
|
||||
}
|
||||
|
||||
public boolean doesGhRepoExist(String owner, String name)
|
||||
{
|
||||
return doesRepoExist("gh:" + owner + "/" + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that {@link org.pf4j.update.UpdateManager#addRepository} checks if the repo exists, however it throws an exception which is bad
|
||||
*/
|
||||
public boolean doesRepoExist(String id)
|
||||
{
|
||||
return repositories.stream().anyMatch((repo) -> repo.getId().equals(id));
|
||||
}
|
||||
|
||||
private static URL toRepositoryUrl(String owner, String name) throws MalformedURLException
|
||||
{
|
||||
return new URL("https://raw.githubusercontent.com/" + owner + "/" + name + "/master/");
|
||||
}
|
||||
|
||||
public static boolean testRepository(String owner, String name)
|
||||
public static boolean testGHRepository(String owner, String name)
|
||||
{
|
||||
final List<UpdateRepository> repositories = new ArrayList<>();
|
||||
try
|
||||
{
|
||||
repositories.add(new DefaultUpdateRepository("github", new URL("https://raw.githubusercontent.com/" + owner + "/" + name + "/master/")));
|
||||
return testRepository(toRepositoryUrl(owner, name));
|
||||
}
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
return true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean testRepository(URL url)
|
||||
{
|
||||
final List<UpdateRepository> repositories = new ArrayList<>();
|
||||
repositories.add(new DefaultUpdateRepository("repository-testing", url));
|
||||
DefaultPluginManager testPluginManager = new DefaultPluginManager(EXTERNALPLUGIN_DIR.toPath());
|
||||
UpdateManager updateManager = new UpdateManager(testPluginManager, repositories);
|
||||
|
||||
@@ -238,9 +286,63 @@ class ExternalPluginManager
|
||||
}
|
||||
|
||||
public void startExternalUpdateManager()
|
||||
{
|
||||
if (!tryLoadNewFormat())
|
||||
{
|
||||
loadOldFormat();
|
||||
}
|
||||
|
||||
this.updateManager = new UpdateManager(this.externalPluginManager, repositories);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
public boolean tryLoadNewFormat()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
|
||||
{
|
||||
String[] split = keyval.split("\\|");
|
||||
if (split.length != 2)
|
||||
{
|
||||
repositories.clear();
|
||||
return false;
|
||||
}
|
||||
String id = split[0];
|
||||
String url = split[1];
|
||||
if (!url.endsWith("/"))
|
||||
{
|
||||
url = url.concat("/");
|
||||
}
|
||||
|
||||
if (id.contains("https://raw.githubusercontent.com/"))
|
||||
{
|
||||
id = "gh:" + id.substring(id.indexOf("https://raw.githubusercontent.com/")).replace("/master", "")
|
||||
.replace("https://raw.githubusercontent.com/", "");
|
||||
|
||||
if (id.endsWith("/"))
|
||||
{
|
||||
id = id.substring(0, id.lastIndexOf("/"));
|
||||
}
|
||||
}
|
||||
|
||||
repositories.add(new DefaultUpdateRepository(id, new URL(url)));
|
||||
}
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException | MalformedURLException e)
|
||||
{
|
||||
repositories.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void loadOldFormat()
|
||||
{
|
||||
try
|
||||
{
|
||||
repositories.clear();
|
||||
|
||||
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
|
||||
{
|
||||
String id = keyval.substring(0, keyval.lastIndexOf(":https"));
|
||||
@@ -262,14 +364,11 @@ class ExternalPluginManager
|
||||
this.updateManager = new UpdateManager(this.externalPluginManager, repositories);
|
||||
}
|
||||
|
||||
public void addRepository(String owner, String name)
|
||||
public void addGHRepository(String owner, String name)
|
||||
{
|
||||
try
|
||||
{
|
||||
DefaultUpdateRepository respository = new DefaultUpdateRepository(owner + toRepositoryUrl(owner, name), toRepositoryUrl(owner, name));
|
||||
updateManager.addRepository(respository);
|
||||
eventBus.post(ExternalRepositoryChanged.class, new ExternalRepositoryChanged(owner + toRepositoryUrl(owner, name), true));
|
||||
saveConfig();
|
||||
addRepository("gh:" + owner + "/" + name, toRepositoryUrl(owner, name));
|
||||
}
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
@@ -277,6 +376,14 @@ class ExternalPluginManager
|
||||
}
|
||||
}
|
||||
|
||||
public void addRepository(String key, URL url)
|
||||
{
|
||||
DefaultUpdateRepository respository = new DefaultUpdateRepository(key, url);
|
||||
updateManager.addRepository(respository);
|
||||
eventBus.post(ExternalRepositoryChanged.class, new ExternalRepositoryChanged(key, true));
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
public void removeRepository(String owner)
|
||||
{
|
||||
updateManager.removeRepository(owner);
|
||||
@@ -291,8 +398,8 @@ class ExternalPluginManager
|
||||
for (UpdateRepository repository : updateManager.getRepositories())
|
||||
{
|
||||
config.append(repository.getId());
|
||||
config.append(":");
|
||||
config.append(repository.getUrl().toString());
|
||||
config.append("|");
|
||||
config.append(MiscUtils.urlToStringEncoded(repository.getUrl()));
|
||||
config.append(";");
|
||||
}
|
||||
config.deleteCharAt(config.lastIndexOf(";"));
|
||||
@@ -333,7 +440,8 @@ class ExternalPluginManager
|
||||
}
|
||||
else if (pluginDescriptor.type() == PluginType.EXTERNAL)
|
||||
{
|
||||
log.error("Class {} is using the the new external plugin loader, it should not use PluginType.EXTERNAL", clazz);
|
||||
log.error("Class {} is using the the new external plugin loader, it should not use PluginType.EXTERNAL",
|
||||
clazz);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -373,16 +481,21 @@ class ExternalPluginManager
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Plugin instantiate(List<Plugin> scannedPlugins, Class<Plugin> clazz, boolean init, boolean initConfig) throws PluginInstantiationException
|
||||
private Plugin instantiate(List<Plugin> scannedPlugins, Class<Plugin> clazz, boolean init, boolean initConfig)
|
||||
throws PluginInstantiationException
|
||||
{
|
||||
net.runelite.client.plugins.PluginDependency[] pluginDependencies = clazz.getAnnotationsByType(net.runelite.client.plugins.PluginDependency.class);
|
||||
net.runelite.client.plugins.PluginDependency[] pluginDependencies =
|
||||
clazz.getAnnotationsByType(net.runelite.client.plugins.PluginDependency.class);
|
||||
List<Plugin> deps = new ArrayList<>();
|
||||
for (net.runelite.client.plugins.PluginDependency pluginDependency : pluginDependencies)
|
||||
{
|
||||
Optional<Plugin> dependency = Stream.concat(runelitePluginManager.getPlugins().stream(), scannedPlugins.stream()).filter(p -> p.getClass() == pluginDependency.value()).findFirst();
|
||||
Optional<Plugin> dependency =
|
||||
Stream.concat(runelitePluginManager.getPlugins().stream(), scannedPlugins.stream())
|
||||
.filter(p -> p.getClass() == pluginDependency.value()).findFirst();
|
||||
if (!dependency.isPresent())
|
||||
{
|
||||
throw new PluginInstantiationException("Unmet dependency for " + clazz.getSimpleName() + ": " + pluginDependency.value().getSimpleName());
|
||||
throw new PluginInstantiationException(
|
||||
"Unmet dependency for " + clazz.getSimpleName() + ": " + pluginDependency.value().getSimpleName());
|
||||
}
|
||||
deps.add(dependency.get());
|
||||
}
|
||||
@@ -444,7 +557,9 @@ class ExternalPluginManager
|
||||
{
|
||||
runelitePluginManager.startPlugin(plugin);
|
||||
runelitePluginManager.add(plugin);
|
||||
eventBus.post(ExternalPluginChanged.class, new ExternalPluginChanged(pluginsMap.get(plugin.getClass().getSimpleName()), plugin, true));
|
||||
eventBus.post(ExternalPluginChanged.class,
|
||||
new ExternalPluginChanged(pluginsMap.get(plugin.getClass().getSimpleName()), plugin,
|
||||
true));
|
||||
}
|
||||
catch (PluginInstantiationException e)
|
||||
{
|
||||
@@ -479,6 +594,21 @@ class ExternalPluginManager
|
||||
|
||||
for (PluginWrapper plugin : startedPlugins)
|
||||
{
|
||||
boolean depsLoaded = true;
|
||||
for (PluginDependency dependency : plugin.getDescriptor().getDependencies())
|
||||
{
|
||||
if (startedPlugins.stream().noneMatch(pl -> pl.getPluginId().equals(dependency.getPluginId())))
|
||||
{
|
||||
depsLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!depsLoaded)
|
||||
{
|
||||
// This should never happen but can crash the client
|
||||
continue;
|
||||
}
|
||||
|
||||
scannedPlugins.addAll(loadPlugin(plugin.getPluginId()));
|
||||
}
|
||||
|
||||
@@ -630,7 +760,7 @@ class ExternalPluginManager
|
||||
try
|
||||
{
|
||||
SwingUtil.syncExec(() ->
|
||||
JOptionPane.showMessageDialog(null,
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
||||
pluginId + " is outdated and cannot be installed",
|
||||
"Installation error",
|
||||
JOptionPane.ERROR_MESSAGE));
|
||||
@@ -678,6 +808,7 @@ class ExternalPluginManager
|
||||
|
||||
public void update()
|
||||
{
|
||||
boolean error = false;
|
||||
if (updateManager.hasUpdates())
|
||||
{
|
||||
List<PluginInfo> updates = updateManager.getUpdates();
|
||||
@@ -692,14 +823,24 @@ class ExternalPluginManager
|
||||
if (!updated)
|
||||
{
|
||||
log.warn("Cannot update plugin '{}'", plugin.id);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
catch (PluginRuntimeException ex)
|
||||
{
|
||||
log.warn("Cannot update plugin '{}', the user probably has another client open", plugin.id);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
initPluginManager();
|
||||
startExternalUpdateManager();
|
||||
startExternalPluginManager();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getDependencies()
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import lombok.AccessLevel;
|
||||
|
||||
@@ -53,8 +53,6 @@ public @interface PluginDescriptor
|
||||
*/
|
||||
boolean hidden() default false;
|
||||
|
||||
boolean developerPlugin() default false;
|
||||
|
||||
boolean loadWhenOutdated() default false;
|
||||
|
||||
PluginType type() default PluginType.UNCATEGORIZED;
|
||||
|
||||
@@ -56,7 +56,6 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -87,7 +86,6 @@ public class PluginManager
|
||||
*/
|
||||
private static final String PLUGIN_PACKAGE = "net.runelite.client.plugins";
|
||||
|
||||
private final boolean developerMode;
|
||||
private final EventBus eventBus;
|
||||
private final Scheduler scheduler;
|
||||
private final ConfigManager configManager;
|
||||
@@ -108,13 +106,11 @@ public class PluginManager
|
||||
@Inject
|
||||
@VisibleForTesting
|
||||
PluginManager(
|
||||
@Named("developerMode") final boolean developerMode,
|
||||
final EventBus eventBus,
|
||||
final Scheduler scheduler,
|
||||
final ConfigManager configManager,
|
||||
final Provider<GameEventManager> sceneTileManager)
|
||||
{
|
||||
this.developerMode = developerMode;
|
||||
this.eventBus = eventBus;
|
||||
this.scheduler = scheduler;
|
||||
this.configManager = configManager;
|
||||
@@ -355,11 +351,6 @@ public class PluginManager
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pluginDescriptor.developerPlugin() && !developerMode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") Class<Plugin> pluginClass = (Class<Plugin>) clazz;
|
||||
graph.addNode(pluginClass);
|
||||
}
|
||||
@@ -462,10 +453,12 @@ public class PluginManager
|
||||
}
|
||||
catch (ThreadDeath e)
|
||||
{
|
||||
activePlugins.remove(plugin);
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable ex)
|
||||
{
|
||||
activePlugins.remove(plugin);
|
||||
throw new PluginInstantiationException(ex);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
@@ -672,11 +671,7 @@ class ConfigPanel extends PluginPanel
|
||||
|
||||
if (units != null)
|
||||
{
|
||||
DecimalFormat df = ((JSpinner.NumberEditor) spinner.getEditor()).getFormat();
|
||||
df.setPositiveSuffix(units.value());
|
||||
df.setNegativeSuffix(units.value());
|
||||
// Force update the spinner to have it add the units initially
|
||||
spinnerTextField.setValue(value);
|
||||
spinnerTextField.setFormatterFactory(new UnitFormatterFactory(units));
|
||||
}
|
||||
|
||||
item.add(spinner, BorderLayout.EAST);
|
||||
@@ -812,11 +807,7 @@ class ConfigPanel extends PluginPanel
|
||||
|
||||
if (units != null)
|
||||
{
|
||||
DecimalFormat df = ((JSpinner.NumberEditor) widthSpinner.getEditor()).getFormat();
|
||||
df.setPositiveSuffix(units.value());
|
||||
df.setNegativeSuffix(units.value());
|
||||
// Force update the spinner to have it add the units initially
|
||||
widthSpinnerTextField.setValue(width);
|
||||
widthSpinnerTextField.setFormatterFactory(new UnitFormatterFactory(units));
|
||||
}
|
||||
|
||||
SpinnerModel heightModel = new SpinnerNumberModel(height, 0, Integer.MAX_VALUE, 1);
|
||||
@@ -827,11 +818,7 @@ class ConfigPanel extends PluginPanel
|
||||
|
||||
if (units != null)
|
||||
{
|
||||
DecimalFormat df = ((JSpinner.NumberEditor) heightSpinner.getEditor()).getFormat();
|
||||
df.setPositiveSuffix(units.value());
|
||||
df.setNegativeSuffix(units.value());
|
||||
// Force update the spinner to have it add the units initially
|
||||
heightSpinnerTextField.setValue(height);
|
||||
heightSpinnerTextField.setFormatterFactory(new UnitFormatterFactory(units));
|
||||
}
|
||||
|
||||
ChangeListener listener = e ->
|
||||
|
||||
@@ -24,13 +24,16 @@
|
||||
*/
|
||||
package net.runelite.client.plugins.config;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.swing.SwingUtilities;
|
||||
import net.runelite.api.MenuOpcode;
|
||||
import net.runelite.client.RuneLiteProperties;
|
||||
import net.runelite.client.config.ChatColorConfig;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.LauncherConfig;
|
||||
import net.runelite.client.config.OpenOSRSConfig;
|
||||
import net.runelite.client.config.RuneLiteConfig;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
@@ -69,6 +72,9 @@ public class ConfigPlugin extends Plugin
|
||||
@Inject
|
||||
private ChatColorConfig chatColorConfig;
|
||||
|
||||
@Inject
|
||||
private LauncherConfig launcherConfig;
|
||||
|
||||
private PluginListPanel pluginListPanel;
|
||||
|
||||
private NavigationButton navButton;
|
||||
@@ -92,6 +98,18 @@ public class ConfigPlugin extends Plugin
|
||||
"Chat Color", "Recolor chat text", PluginType.MISCELLANEOUS, new String[]{"colour", "messages"},
|
||||
null, chatColorConfig, configManager.getConfigDescriptor(chatColorConfig)
|
||||
));
|
||||
|
||||
// Support for this has been added in launcher version 2.2.0
|
||||
if (launcherVersion("2.2.0"))
|
||||
{
|
||||
pluginListPanel.addFakePlugin(
|
||||
new PluginConfigurationDescriptor(
|
||||
"Launcher", "Launcher settings", PluginType.IMPORTANT,
|
||||
new String[]{"hw", "nightly", "stable", "proxy", "bootstrap"},
|
||||
null, launcherConfig, configManager.getConfigDescriptor(launcherConfig)
|
||||
));
|
||||
}
|
||||
|
||||
pluginListPanel.rebuildPluginList();
|
||||
|
||||
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "config_icon.png");
|
||||
@@ -137,4 +155,16 @@ public class ConfigPlugin extends Plugin
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean launcherVersion(String version)
|
||||
{
|
||||
String launcherVersion = RuneLiteProperties.getLauncherVersion();
|
||||
|
||||
if (launcherVersion == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Version.valueOf(launcherVersion).greaterThanOrEqualTo(Version.valueOf(version));
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,9 @@ import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||
|
||||
public 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_STAR;
|
||||
@@ -61,6 +58,7 @@ public class PluginListItem extends JPanel
|
||||
@Getter
|
||||
private final PluginConfigurationDescriptor pluginConfig;
|
||||
|
||||
@Getter
|
||||
private final List<String> keywords = new ArrayList<>();
|
||||
|
||||
public JLabel nameLabel;
|
||||
@@ -201,24 +199,6 @@ public class PluginListItem extends JPanel
|
||||
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)
|
||||
{
|
||||
for (String term : searchTerms)
|
||||
{
|
||||
if (keywords.stream().noneMatch((t) -> t.contains(term) ||
|
||||
DISTANCE.apply(t, term) > 0.9))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openGroupConfigPanel()
|
||||
{
|
||||
pluginListPanel.openConfigurationPanel(pluginConfig);
|
||||
|
||||
@@ -381,7 +381,7 @@ public class PluginListPanel extends PluginPanel
|
||||
final String[] searchTerms = text.toLowerCase().split(" ");
|
||||
pluginList.forEach(listItem ->
|
||||
{
|
||||
if (pinned == listItem.isPinned() && listItem.matchesSearchTerms(searchTerms))
|
||||
if (pinned == listItem.isPinned() && Text.matchesSearchTerms(searchTerms, listItem.getKeywords()))
|
||||
{
|
||||
if (openOSRSConfig.pluginSortMode() == OpenOSRSConfig.SortStyle.ALPHABETICALLY || !openOSRSConfig.enableCategories())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.plugins.config;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.runelite.client.config.Units;
|
||||
|
||||
final class UnitFormatter extends JFormattedTextField.AbstractFormatter
|
||||
{
|
||||
private final String units;
|
||||
|
||||
UnitFormatter(Units units)
|
||||
{
|
||||
this.units = units.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object stringToValue(final String text) throws ParseException
|
||||
{
|
||||
final String trimmedText;
|
||||
|
||||
// Using the spinner controls causes the value to have the unit on the end, so remove it
|
||||
if (text.endsWith(units))
|
||||
{
|
||||
trimmedText = text.substring(0, text.length() - units.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
trimmedText = text;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Integer.valueOf(trimmedText);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new ParseException(trimmedText + " is not an integer.", 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String valueToString(final Object value)
|
||||
{
|
||||
return value + units;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
final class UnitFormatterFactory extends JFormattedTextField.AbstractFormatterFactory
|
||||
{
|
||||
private final Units units;
|
||||
private final Map<JFormattedTextField, JFormattedTextField.AbstractFormatter> formatters = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public JFormattedTextField.AbstractFormatter getFormatter(final JFormattedTextField tf)
|
||||
{
|
||||
return formatters.computeIfAbsent(tf, (key) -> new UnitFormatter(units));
|
||||
}
|
||||
}
|
||||
@@ -79,11 +79,14 @@ class InfoPanel extends PluginPanel
|
||||
}
|
||||
|
||||
private JPanel syncPanel;
|
||||
|
||||
@Inject
|
||||
@Nullable
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private ConfigManager configManager;
|
||||
|
||||
@Inject
|
||||
private InfoPlugin plugin;
|
||||
|
||||
@@ -104,8 +107,12 @@ class InfoPanel extends PluginPanel
|
||||
JLabel version = new JLabel(htmlLabel("RuneLite version: ", RuneLiteProperties.getVersion()));
|
||||
version.setFont(smallFont);
|
||||
|
||||
JLabel plusVersion = new JLabel(htmlLabel("OpenOSRS version: ", RuneLiteProperties.getPlusVersion()));
|
||||
plusVersion.setFont(smallFont);
|
||||
JLabel openOsrsVersion = new JLabel(htmlLabel("OpenOSRS version: ", RuneLiteProperties.getPlusVersion()));
|
||||
openOsrsVersion.setFont(smallFont);
|
||||
|
||||
String launcher = RuneLiteProperties.getLauncherVersion();
|
||||
JLabel launcherVersion = new JLabel(htmlLabel("Launcher version: ", launcher));
|
||||
launcherVersion.setFont(smallFont);
|
||||
|
||||
JLabel revision = new JLabel();
|
||||
revision.setFont(smallFont);
|
||||
@@ -119,7 +126,11 @@ class InfoPanel extends PluginPanel
|
||||
revision.setText(htmlLabel("Oldschool revision: ", engineVer));
|
||||
|
||||
versionPanel.add(version);
|
||||
versionPanel.add(plusVersion);
|
||||
versionPanel.add(openOsrsVersion);
|
||||
if (launcher != null)
|
||||
{
|
||||
versionPanel.add(launcherVersion);
|
||||
}
|
||||
versionPanel.add(revision);
|
||||
|
||||
JPanel actionsContainer = new JPanel();
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.awt.Dimension;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -18,23 +20,28 @@ import javax.swing.JTextField;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.plugins.ExternalPluginManager;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
|
||||
public class ExternalPluginManagerPanel extends PluginPanel
|
||||
{
|
||||
private static final ImageIcon ADD_ICON;
|
||||
private static final ImageIcon ADD_HOVER_ICON;
|
||||
private static final ImageIcon ADD_ICON_RAW;
|
||||
private static final ImageIcon ADD_HOVER_ICON_RAW;
|
||||
private static final ImageIcon ADD_ICON_GH;
|
||||
private static final ImageIcon ADD_HOVER_ICON_GH;
|
||||
|
||||
static
|
||||
{
|
||||
final BufferedImage addIcon =
|
||||
ImageUtil.recolorImage(
|
||||
ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "add_icon.png"), ColorScheme.BRAND_BLUE
|
||||
);
|
||||
ADD_ICON = new ImageIcon(addIcon);
|
||||
ADD_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f));
|
||||
final BufferedImage addIconRaw =
|
||||
ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "add_raw_icon.png");
|
||||
final BufferedImage addIconGh = ImageUtil
|
||||
.resizeImage(ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "gh_icon.png"), 14, 14);
|
||||
ADD_ICON_RAW = new ImageIcon(addIconRaw);
|
||||
ADD_HOVER_ICON_RAW = new ImageIcon(ImageUtil.alphaOffset(addIconRaw, 0.53f));
|
||||
ADD_ICON_GH = new ImageIcon(addIconGh);
|
||||
ADD_HOVER_ICON_GH = new ImageIcon(ImageUtil.alphaOffset(addIconGh, 0.53f));
|
||||
}
|
||||
|
||||
private final ExternalPluginManager externalPluginManager;
|
||||
@@ -73,13 +80,17 @@ public class ExternalPluginManagerPanel extends PluginPanel
|
||||
titlePanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
|
||||
JLabel title = new JLabel();
|
||||
JLabel addRepo = new JLabel(ADD_ICON);
|
||||
JLabel addGHRepo = new JLabel(ADD_ICON_GH);
|
||||
JLabel addRawRepo = new JLabel(ADD_ICON_RAW);
|
||||
|
||||
JPanel buttonHolder = new JPanel(new BorderLayout());
|
||||
buttonHolder.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
|
||||
title.setText("External Plugin Manager");
|
||||
title.setForeground(Color.WHITE);
|
||||
|
||||
addRepo.setToolTipText("Add new repository");
|
||||
addRepo.addMouseListener(new MouseAdapter()
|
||||
addGHRepo.setToolTipText("Add new GitHub repository");
|
||||
addGHRepo.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
@@ -91,36 +102,120 @@ public class ExternalPluginManagerPanel extends PluginPanel
|
||||
"Github Repository name:", name
|
||||
};
|
||||
|
||||
int option = JOptionPane.showConfirmDialog(null, message, "Add repository", JOptionPane.OK_CANCEL_OPTION);
|
||||
int option =
|
||||
JOptionPane.showConfirmDialog(ClientUI.getFrame(), message, "Add repository", JOptionPane.OK_CANCEL_OPTION);
|
||||
if (option != JOptionPane.OK_OPTION || owner.getText().equals("") || name.getText().equals(""))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExternalPluginManager.testRepository(owner.getText(), name.getText()))
|
||||
if (externalPluginManager.doesGhRepoExist(owner.getText(), name.getText()))
|
||||
{
|
||||
JOptionPane.showMessageDialog(null, "This doesn't appear to be a valid repository.", "Error!", JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "This repository already exists.", "Error!",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
externalPluginManager.addRepository(owner.getText(), name.getText());
|
||||
if (ExternalPluginManager.testGHRepository(owner.getText(), name.getText()))
|
||||
{
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "This doesn't appear to be a valid repository.", "Error!",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
externalPluginManager.addGHRepository(owner.getText(), name.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent)
|
||||
{
|
||||
addRepo.setIcon(ADD_HOVER_ICON);
|
||||
addGHRepo.setIcon(ADD_HOVER_ICON_GH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent)
|
||||
{
|
||||
addRepo.setIcon(ADD_ICON);
|
||||
addGHRepo.setIcon(ADD_ICON_GH);
|
||||
}
|
||||
});
|
||||
addGHRepo.setBorder(new EmptyBorder(0, 3, 0, 0));
|
||||
|
||||
addRawRepo.setToolTipText("Add new raw repository");
|
||||
addRawRepo.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
JTextField id = new JTextField();
|
||||
JTextField url = new JTextField();
|
||||
Object[] message = {
|
||||
"Repository ID:", id,
|
||||
"Repository URL:", url
|
||||
};
|
||||
|
||||
int option =
|
||||
JOptionPane.showConfirmDialog(ClientUI.getFrame(), message, "Add repository", JOptionPane.OK_CANCEL_OPTION);
|
||||
if (option != JOptionPane.OK_OPTION || id.getText().equals("") || url.getText().equals(""))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (id.getText().startsWith("gh:") || id.getText().contains("|"))
|
||||
{
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
||||
"Repository id cannot begin with \"gh:\"\nor contain the pipe character '|'.", "Error!",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (externalPluginManager.doesRepoExist(id.getText()))
|
||||
{
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
||||
String.format("The repository with id %s already exists.", id.getText()), "Error!",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
URL urlActual;
|
||||
try
|
||||
{
|
||||
urlActual = new URL(url.getText());
|
||||
}
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "This doesn't appear to be a valid repository.", "Error!",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExternalPluginManager.testRepository(urlActual))
|
||||
{
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "This doesn't appear to be a valid repository.", "Error!",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
externalPluginManager.addRepository(id.getText(), urlActual);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent)
|
||||
{
|
||||
addRawRepo.setIcon(ADD_HOVER_ICON_RAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent)
|
||||
{
|
||||
addRawRepo.setIcon(ADD_ICON_RAW);
|
||||
}
|
||||
});
|
||||
addRawRepo.setBorder(new EmptyBorder(0, 0, 0, 3));
|
||||
|
||||
titlePanel.add(title, BorderLayout.WEST);
|
||||
titlePanel.add(addRepo, BorderLayout.EAST);
|
||||
buttonHolder.add(addRawRepo, BorderLayout.WEST);
|
||||
buttonHolder.add(addGHRepo, BorderLayout.EAST);
|
||||
titlePanel.add(buttonHolder, BorderLayout.EAST);
|
||||
|
||||
return titlePanel;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import static net.runelite.api.util.Text.DISTANCE;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.events.ExternalPluginChanged;
|
||||
import net.runelite.client.events.ExternalRepositoryChanged;
|
||||
import net.runelite.client.plugins.ExternalPluginManager;
|
||||
import static net.runelite.client.plugins.openosrs.externals.ExternalPluginManagerPanel.wrapContainer;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
@@ -43,7 +45,6 @@ import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
||||
import net.runelite.client.util.DeferredDocumentChangedListener;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||
import org.pf4j.update.PluginInfo;
|
||||
import org.pf4j.update.UpdateManager;
|
||||
import org.pf4j.update.UpdateRepository;
|
||||
@@ -52,7 +53,6 @@ import org.pf4j.update.VerifyException;
|
||||
@Slf4j
|
||||
public class PluginsPanel extends JPanel
|
||||
{
|
||||
private static final JaroWinklerDistance DISTANCE = new JaroWinklerDistance();
|
||||
private static final ImageIcon ADD_ICON;
|
||||
private static final ImageIcon ADD_HOVER_ICON;
|
||||
private static final ImageIcon DELETE_ICON;
|
||||
@@ -257,7 +257,7 @@ public class PluginsPanel extends JPanel
|
||||
|
||||
if (availablePlugins == null || plugins == null)
|
||||
{
|
||||
JOptionPane.showMessageDialog(null, "The external plugin list could not be loaded.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "The external plugin list could not be loaded.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ public class PluginsPanel extends JPanel
|
||||
{
|
||||
if (hideAction)
|
||||
{
|
||||
JOptionPane.showMessageDialog(null, "This plugin can't be uninstalled because one or more other plugins have a dependency on it.", "Error!", JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "This plugin can't be uninstalled because one or more other plugins have a dependency on it.", "Error!", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -573,7 +573,7 @@ public class PluginsPanel extends JPanel
|
||||
try
|
||||
{
|
||||
SwingUtil.syncExec(() ->
|
||||
JOptionPane.showMessageDialog(null, pluginInfo.name + " could not be installed, the hash could not be verified.", "Error!", JOptionPane.ERROR_MESSAGE));
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), pluginInfo.name + " could not be installed, the hash could not be verified.", "Error!", JOptionPane.ERROR_MESSAGE));
|
||||
}
|
||||
catch (InvocationTargetException | InterruptedException ignored)
|
||||
{
|
||||
|
||||
@@ -58,8 +58,12 @@ public class RepositoryBox extends JPanel
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
|
||||
String name = updateRepository.getId().replace(updateRepository.getUrl().toString(), "");
|
||||
String urlString = updateRepository.getUrl().toString().replace("https://raw.githubusercontent.com/", "").replace("/master/", "");
|
||||
String name = updateRepository.getId();
|
||||
String urlString = updateRepository.getUrl().toString();
|
||||
if (urlString.startsWith("/"))
|
||||
{
|
||||
urlString = urlString.substring(1);
|
||||
}
|
||||
|
||||
JPanel titleWrapper = new JPanel(new BorderLayout());
|
||||
titleWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
@@ -145,11 +149,31 @@ public class RepositoryBox extends JPanel
|
||||
titleWrapper.add(titleActions, BorderLayout.EAST);
|
||||
|
||||
JMultilineLabel repository = new JMultilineLabel();
|
||||
repository.setText(urlString.split("/", 2)[1]);
|
||||
repository.setText(formatURL(urlString));
|
||||
repository.setFont(smallFont);
|
||||
repository.setDisabledTextColor(Color.WHITE);
|
||||
|
||||
String finalUrlString = urlString;
|
||||
repository.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
LinkBrowser.browse(formatURL(finalUrlString));
|
||||
}
|
||||
});
|
||||
|
||||
add(titleWrapper, BorderLayout.NORTH);
|
||||
add(repository, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private String formatURL(String url)
|
||||
{
|
||||
if (url.contains("githubusercontent"))
|
||||
{
|
||||
url = url.replace("raw.githubusercontent", "github").replace("/master/", "");
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.applet.Applet;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import net.runelite.api.Client;
|
||||
@@ -65,13 +64,13 @@ final class ClientPanel extends JPanel
|
||||
{
|
||||
String message = "Detected a bad codebase. Resetting...\n"
|
||||
+ "Please restart client.\n";
|
||||
JOptionPane.showMessageDialog(new JFrame(), message, "Bad Codebase",
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), message, "Bad Codebase",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
StringFileUtils.writeStringToFile(RuneLite.RUNELITE_DIR + "/codebase", "http://127.0.0.1/");
|
||||
}
|
||||
else
|
||||
{
|
||||
JOptionPane.showMessageDialog(new JFrame(), "Error loading Oldschool RuneScape!", "Error",
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(), "Error loading Oldschool RuneScape!", "Error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
Sentry.capture(e);
|
||||
}
|
||||
|
||||
@@ -667,7 +667,7 @@ public class ClientUI
|
||||
return;
|
||||
}
|
||||
|
||||
final java.awt.Point hotspot = new java.awt.Point(container.getX(), container.getY());
|
||||
final java.awt.Point hotspot = new java.awt.Point(0, 0);
|
||||
final Cursor cursorAwt = Toolkit.getDefaultToolkit().createCustomCursor(image, hotspot, name);
|
||||
container.setCursor(cursorAwt);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Shingyx <https://github.com/Shingyx>
|
||||
* 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.components;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.LayoutManager;
|
||||
import java.awt.Point;
|
||||
import java.awt.dnd.DragSource;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JLayeredPane;
|
||||
|
||||
/**
|
||||
* Pane which allows reordering its components via drag and drop.
|
||||
* <p>
|
||||
* For child components' popup menus, implement the PopupMenuOwner interface in the child components.
|
||||
*/
|
||||
public class DragAndDropReorderPane extends JLayeredPane
|
||||
{
|
||||
private Point dragStartPoint;
|
||||
private Component draggingComponent;
|
||||
private int dragIndex = -1;
|
||||
|
||||
private final Map<Integer, PopupMenuOwner> popupMenuCandidates = new HashMap<>(); // keyed by mouse button
|
||||
|
||||
public DragAndDropReorderPane()
|
||||
{
|
||||
super();
|
||||
setLayout(new DragAndDropReorderLayoutManager());
|
||||
MouseAdapter mouseAdapter = new DragAndDropReorderMouseAdapter();
|
||||
addMouseListener(mouseAdapter);
|
||||
addMouseMotionListener(mouseAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayout(LayoutManager layoutManager)
|
||||
{
|
||||
if (layoutManager != null && !(layoutManager instanceof DragAndDropReorderLayoutManager))
|
||||
{
|
||||
throw new IllegalArgumentException("DragAndDropReorderPane only supports DragAndDropReorderLayoutManager");
|
||||
}
|
||||
super.setLayout(layoutManager);
|
||||
}
|
||||
|
||||
private void startDragging(Point point)
|
||||
{
|
||||
draggingComponent = getDefaultLayerComponentAt(dragStartPoint);
|
||||
if (draggingComponent == null)
|
||||
{
|
||||
dragStartPoint = null;
|
||||
return;
|
||||
}
|
||||
dragIndex = getPosition(draggingComponent);
|
||||
setLayer(draggingComponent, DRAG_LAYER);
|
||||
moveDraggingComponent(point);
|
||||
}
|
||||
|
||||
private void drag(Point point)
|
||||
{
|
||||
moveDraggingComponent(point);
|
||||
|
||||
Component component = getDefaultLayerComponentAt(point);
|
||||
if (component != null)
|
||||
{
|
||||
int index = getPosition(component);
|
||||
dragIndex = index < dragIndex ? index : index + 1;
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishDragging()
|
||||
{
|
||||
if (draggingComponent != null)
|
||||
{
|
||||
setLayer(draggingComponent, DEFAULT_LAYER, dragIndex);
|
||||
revalidate();
|
||||
}
|
||||
dragStartPoint = null;
|
||||
draggingComponent = null;
|
||||
dragIndex = -1;
|
||||
}
|
||||
|
||||
private void moveDraggingComponent(Point point)
|
||||
{
|
||||
// place the center of the dragging component onto the mouse cursor
|
||||
int y = point.y - draggingComponent.getHeight() / 2;
|
||||
// clamp the height to stay within the pane
|
||||
y = Math.max(y, 0);
|
||||
y = Math.min(y, getHeight() - draggingComponent.getHeight());
|
||||
|
||||
draggingComponent.setLocation(new Point(0, y));
|
||||
}
|
||||
|
||||
private Component getDefaultLayerComponentAt(Point point)
|
||||
{
|
||||
for (Component component : getComponentsInLayer(DEFAULT_LAYER))
|
||||
{
|
||||
if (component.contains(point.x - component.getX(), point.y - component.getY()))
|
||||
{
|
||||
return component;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class DragAndDropReorderLayoutManager extends BoxLayout
|
||||
{
|
||||
private DragAndDropReorderLayoutManager()
|
||||
{
|
||||
super(DragAndDropReorderPane.this, BoxLayout.Y_AXIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layoutContainer(Container target)
|
||||
{
|
||||
if (draggingComponent != null)
|
||||
{
|
||||
// temporarily move the dragging component to the default layer for correct layout calculation
|
||||
Point location = draggingComponent.getLocation();
|
||||
setLayer(draggingComponent, DEFAULT_LAYER, dragIndex);
|
||||
super.layoutContainer(target);
|
||||
setLayer(draggingComponent, DRAG_LAYER);
|
||||
draggingComponent.setLocation(location);
|
||||
}
|
||||
else
|
||||
{
|
||||
super.layoutContainer(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DragAndDropReorderMouseAdapter extends MouseAdapter
|
||||
{
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e)
|
||||
{
|
||||
Point point = e.getPoint();
|
||||
int mouseButton = e.getButton();
|
||||
|
||||
if (mouseButton == MouseEvent.BUTTON1)
|
||||
{
|
||||
// candidate for dragging
|
||||
if (popupMenuCandidates.isEmpty() && getComponentCount() > 1)
|
||||
{
|
||||
dragStartPoint = point;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dragStartPoint != null)
|
||||
{
|
||||
finishDragging();
|
||||
}
|
||||
else
|
||||
{
|
||||
// candidate for child popup menu
|
||||
Component component = getDefaultLayerComponentAt(point);
|
||||
if (component instanceof PopupMenuOwner)
|
||||
{
|
||||
PopupMenuOwner popupMenuCandidate = (PopupMenuOwner) component;
|
||||
if (e.isPopupTrigger())
|
||||
{
|
||||
popupMenuCandidate.getPopupMenu().show(DragAndDropReorderPane.this, point.x, point.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
popupMenuCandidates.put(mouseButton, popupMenuCandidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e)
|
||||
{
|
||||
if (dragStartPoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Point point = e.getPoint();
|
||||
if (contains(point))
|
||||
{
|
||||
if (draggingComponent == null)
|
||||
{
|
||||
if (point.distance(dragStartPoint) > DragSource.getDragThreshold())
|
||||
{
|
||||
startDragging(point);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
drag(point);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
finishDragging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e)
|
||||
{
|
||||
Point point = e.getPoint();
|
||||
int mouseButton = e.getButton();
|
||||
if (mouseButton == MouseEvent.BUTTON1)
|
||||
{
|
||||
finishDragging();
|
||||
}
|
||||
else
|
||||
{
|
||||
PopupMenuOwner popupMenuCandidate = popupMenuCandidates.remove(mouseButton);
|
||||
if (popupMenuCandidate != null && e.isPopupTrigger())
|
||||
{
|
||||
popupMenuCandidate.getPopupMenu().show(DragAndDropReorderPane.this, point.x, point.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Shingyx <https://github.com/Shingyx>
|
||||
* 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.components;
|
||||
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
/**
|
||||
* Represents a UI component which has a popup menu, to be used on child components inside DragAndDropReorderPane.
|
||||
* This is because using setComponentPopupMenu consumes the MouseEvents required for drag and drop reordering.
|
||||
*/
|
||||
public interface PopupMenuOwner
|
||||
{
|
||||
JPopupMenu getPopupMenu();
|
||||
}
|
||||
@@ -63,6 +63,7 @@ public class WidgetOverlay extends Overlay
|
||||
.put(WidgetInfo.LMS_INFO, OverlayPosition.TOP_CENTER)
|
||||
.put(WidgetInfo.LMS_KDA, OverlayPosition.TOP_CENTER)
|
||||
.put(WidgetInfo.THEATRE_OF_BLOOD_HEALTH_ORBS, OverlayPosition.TOP_LEFT)
|
||||
.put(WidgetInfo.GAUNTLET_TIMER_CONTAINER, OverlayPosition.TOP_LEFT)
|
||||
.build();
|
||||
|
||||
public static Collection<WidgetOverlay> createOverlays(final Client client)
|
||||
|
||||
@@ -44,7 +44,6 @@ import net.runelite.client.ui.overlay.components.TooltipComponent;
|
||||
public class TooltipOverlay extends Overlay
|
||||
{
|
||||
private static final int UNDER_OFFSET = 24;
|
||||
private static final int ABOVE_OFFSET = -20;
|
||||
private static final int PADDING = 2;
|
||||
private final TooltipManager tooltipManager;
|
||||
private final Client client;
|
||||
@@ -84,50 +83,28 @@ public class TooltipOverlay extends Overlay
|
||||
|
||||
private Dimension renderTooltips(Graphics2D graphics, List<Tooltip> tooltips)
|
||||
{
|
||||
final Rectangle clientCanvasBounds = new Rectangle(client.getRealDimensions());
|
||||
final int canvasWidth = client.getCanvasWidth();
|
||||
final int canvasHeight = client.getCanvasHeight();
|
||||
final net.runelite.api.Point mouseCanvasPosition = client.getMouseCanvasPosition();
|
||||
final int offset = runeLiteConfig.tooltipPosition() == TooltipPositionType.UNDER_CURSOR ? UNDER_OFFSET : ABOVE_OFFSET;
|
||||
final Point mousePosition = new Point(mouseCanvasPosition.getX(), mouseCanvasPosition.getY() + offset);
|
||||
final Rectangle bounds = new Rectangle(getBounds());
|
||||
bounds.setLocation(mousePosition);
|
||||
final Rectangle prevBounds = getBounds();
|
||||
|
||||
if (!clientCanvasBounds.contains(bounds))
|
||||
{
|
||||
final int clientX = (int) clientCanvasBounds.getMaxX();
|
||||
final int clientY = (int) clientCanvasBounds.getMaxY();
|
||||
final int boundsX = (int) bounds.getMaxX();
|
||||
final int boundsY = (int) bounds.getMaxY();
|
||||
final int tooltipX = Math.min(canvasWidth - prevBounds.width, mouseCanvasPosition.getX());
|
||||
final int tooltipY = runeLiteConfig.tooltipPosition() == TooltipPositionType.ABOVE_CURSOR
|
||||
? Math.max(0, mouseCanvasPosition.getY() - prevBounds.height)
|
||||
: Math.min(canvasHeight - prevBounds.height, mouseCanvasPosition.getY() + UNDER_OFFSET);
|
||||
|
||||
if (boundsY > clientY)
|
||||
{
|
||||
graphics.translate(0, -bounds.height - offset);
|
||||
}
|
||||
|
||||
if (boundsX > clientX)
|
||||
{
|
||||
graphics.translate(-bounds.width + clientCanvasBounds.width - bounds.x, 0);
|
||||
}
|
||||
}
|
||||
|
||||
final Rectangle newBounds = new Rectangle(-1, -1, 0, 0);
|
||||
final Rectangle newBounds = new Rectangle(tooltipX, tooltipY, 0, 0);
|
||||
|
||||
for (Tooltip tooltip : tooltips)
|
||||
{
|
||||
final TooltipComponent tooltipComponent = new TooltipComponent();
|
||||
tooltipComponent.setModIcons(client.getModIcons());
|
||||
tooltipComponent.setText(tooltip.getText());
|
||||
tooltipComponent.setPosition(new Point(tooltipX, tooltipY + newBounds.height));
|
||||
|
||||
if (newBounds.contains(mousePosition))
|
||||
{
|
||||
mousePosition.move(mouseCanvasPosition.getX(), mouseCanvasPosition.getY() + offset + newBounds.height);
|
||||
}
|
||||
|
||||
tooltipComponent.setPosition(mousePosition);
|
||||
final Dimension dimension = tooltipComponent.render(graphics);
|
||||
|
||||
// Create incremental tooltip newBounds
|
||||
newBounds.x = newBounds.x != -1 ? Math.min(newBounds.x, mousePosition.x) : mousePosition.x;
|
||||
newBounds.y = newBounds.y != -1 ? Math.min(newBounds.y, mousePosition.y) : mousePosition.y;
|
||||
newBounds.height += dimension.height + PADDING;
|
||||
newBounds.width = Math.max(newBounds.width, dimension.width);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by rumatoest at github.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package net.runelite.client.util;
|
||||
|
||||
/**
|
||||
* The Class AppLock.
|
||||
*
|
||||
* @author Vladislav Zablotsky
|
||||
*/
|
||||
public class AppLock
|
||||
{
|
||||
|
||||
private static CrossLock lockInstance;
|
||||
|
||||
/**
|
||||
* Set lock for application instance.
|
||||
* Method must be run only one time at application start.
|
||||
*
|
||||
* @param lockId Unique lock identifiers
|
||||
* @return true if succeeded
|
||||
*/
|
||||
public synchronized boolean lock(String lockId)
|
||||
{
|
||||
if (lockInstance == null)
|
||||
{
|
||||
lockInstance = new CrossLock("application_" + lockId);
|
||||
}
|
||||
return lockInstance.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to release application lock.
|
||||
* Thus another application instances will be able to use lock with current ID.
|
||||
*/
|
||||
public synchronized void release()
|
||||
{
|
||||
if (lockInstance != null)
|
||||
{
|
||||
lockInstance.clear();
|
||||
}
|
||||
lockInstance = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by rumatoest at github.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package net.runelite.client.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import static net.runelite.client.RuneLite.RUNELITE_DIR;
|
||||
|
||||
/**
|
||||
* Universal cross application instances locker.
|
||||
* Allow you to create simple lock like object which can be used for
|
||||
* different application instances. Basic idea is simple - just a simple file lock.
|
||||
* <br />
|
||||
* All you need is to define unique key for each lock type.
|
||||
*
|
||||
* @author Vladislav Zablotsky
|
||||
*/
|
||||
public class CrossLock
|
||||
{
|
||||
|
||||
private static final HashMap<String, CrossLock> locks = new HashMap<>();
|
||||
|
||||
private final String id;
|
||||
|
||||
private final File fileToLock;
|
||||
|
||||
private FileOutputStream fileStream;
|
||||
|
||||
private FileChannel fileStreamChannel;
|
||||
|
||||
private FileLock lockOnFile;
|
||||
|
||||
/**
|
||||
* Will create or retrieve lock instance.
|
||||
* Each lock id is unique among all you instances,
|
||||
* thus only one instance can acquire lock for this id.
|
||||
*
|
||||
* @param lockId Unique lock identifier
|
||||
* @return Not null
|
||||
*/
|
||||
public static CrossLock get(String lockId)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
return locks.get(lockId);
|
||||
}
|
||||
else
|
||||
{
|
||||
synchronized (CrossLock.class)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
return locks.get(lockId);
|
||||
}
|
||||
else
|
||||
{
|
||||
CrossLock cl = new CrossLock(lockId);
|
||||
locks.put(lockId, cl);
|
||||
return cl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove lock object for specific id and release lock if any.
|
||||
*
|
||||
* @param lockId Unique lock identifier
|
||||
*/
|
||||
public static void remove(String lockId)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
CrossLock lock = null;
|
||||
synchronized (CrossLock.class)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
lock = locks.remove(lockId);
|
||||
}
|
||||
}
|
||||
if (lock != null)
|
||||
{
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CrossLock(String lockId)
|
||||
{
|
||||
this.id = lockId;
|
||||
fileToLock = new File(RUNELITE_DIR, lockId + ".app_lock");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return lock instance identifier.
|
||||
*/
|
||||
public String id()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate lock.
|
||||
* Note! This is only cross application (cross instances) lock. It will not work
|
||||
* as lock inside single application instance.
|
||||
*
|
||||
* @return true if lock was acquire or false
|
||||
*/
|
||||
public synchronized boolean lock()
|
||||
{
|
||||
if (lockOnFile != null && lockOnFile.isValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
String lockContent = "#Java AppLock Object\n#Locked by key: " + id() + "\r\n";
|
||||
try
|
||||
{
|
||||
if (fileToLock.exists())
|
||||
{
|
||||
fileToLock.createNewFile();
|
||||
}
|
||||
fileStream = new FileOutputStream(fileToLock);
|
||||
fileStreamChannel = fileStream.getChannel();
|
||||
lockOnFile = fileStreamChannel.tryLock();
|
||||
if (lockOnFile != null)
|
||||
{
|
||||
fileStream.write(lockContent.getBytes());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!(ex instanceof OverlappingFileLockException))
|
||||
{
|
||||
Logger.getLogger(AppLock.class.getName()).log(Level.WARNING,
|
||||
"Can not get application lock for id=" + id() + "\n" + ex.getMessage(), ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return lockOnFile != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release lock associated with this object.
|
||||
*/
|
||||
public synchronized void release()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lockOnFile != null && lockOnFile.isValid())
|
||||
{
|
||||
lockOnFile.release();
|
||||
}
|
||||
lockOnFile = null;
|
||||
|
||||
if (fileStream != null)
|
||||
{
|
||||
fileStream.close();
|
||||
fileStream = null;
|
||||
}
|
||||
|
||||
if (fileStreamChannel != null && fileStreamChannel.isOpen())
|
||||
{
|
||||
fileStreamChannel.close();
|
||||
}
|
||||
fileStreamChannel = null;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.getLogger(AppLock.class.getName()).log(Level.WARNING,
|
||||
"Can not get application lock for id=" + id() + "\n" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release lock and remove lock file.
|
||||
*/
|
||||
public synchronized void clear()
|
||||
{
|
||||
release();
|
||||
if (fileToLock.exists())
|
||||
{
|
||||
fileToLock.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable
|
||||
{
|
||||
this.clear();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
package net.runelite.client.util;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package net.runelite.client.util;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
@@ -40,6 +41,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -80,10 +82,11 @@ public class ImageCapture
|
||||
*
|
||||
* @param screenshot BufferedImage to capture.
|
||||
* @param fileName Filename to use, without file extension.
|
||||
* @param subDir Directory within the player screenshots dir to store the captured screenshot to.
|
||||
* @param notify Send a notification to the system tray when the image is captured.
|
||||
* @param imageUploadStyle which method to use to upload the screenshot (Imgur or directly to clipboard).
|
||||
*/
|
||||
public void takeScreenshot(BufferedImage screenshot, String fileName, boolean notify, ImageUploadStyle imageUploadStyle)
|
||||
public void takeScreenshot(BufferedImage screenshot, String fileName, @Nullable String subDir, boolean notify, ImageUploadStyle imageUploadStyle)
|
||||
{
|
||||
if (client.getGameState() == GameState.LOGIN_SCREEN)
|
||||
{
|
||||
@@ -106,6 +109,12 @@ public class ImageCapture
|
||||
{
|
||||
playerDir += "-League";
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(subDir))
|
||||
{
|
||||
playerDir += File.separator + subDir;
|
||||
}
|
||||
|
||||
playerFolder = new File(SCREENSHOT_DIR, playerDir);
|
||||
}
|
||||
else
|
||||
@@ -157,6 +166,20 @@ public class ImageCapture
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a screenshot of the client window to the screenshot folder as a PNG,
|
||||
* and optionally uploads it to an image-hosting service.
|
||||
*
|
||||
* @param screenshot BufferedImage to capture.
|
||||
* @param fileName Filename to use, without file extension.
|
||||
* @param notify Send a notification to the system tray when the image is captured.
|
||||
* @param imageUploadStyle which method to use to upload the screenshot (Imgur or directly to clipboard).
|
||||
*/
|
||||
public void takeScreenshot(BufferedImage screenshot, String fileName, boolean notify, ImageUploadStyle imageUploadStyle)
|
||||
{
|
||||
takeScreenshot(screenshot, fileName, null, notify, imageUploadStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a screenshot to the Imgur image-hosting service,
|
||||
* and copies the image link to the clipboard.
|
||||
|
||||
@@ -36,6 +36,7 @@ import javax.inject.Singleton;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
|
||||
/**
|
||||
* Utility class used for web and file browser navigation
|
||||
@@ -249,7 +250,7 @@ public class LinkBrowser
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
final int result = JOptionPane.showConfirmDialog(null, message, "Message",
|
||||
final int result = JOptionPane.showConfirmDialog(ClientUI.getFrame(), message, "Message",
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
|
||||
if (result == JOptionPane.OK_OPTION)
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package net.runelite.client.util;
|
||||
|
||||
import java.awt.Polygon;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.WorldType;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import java.awt.Polygon;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MiscUtils
|
||||
{
|
||||
@@ -170,4 +175,28 @@ public class MiscUtils
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly stolen from {@link java.net.URLStreamHandler#toExternalForm(URL)}
|
||||
*
|
||||
* @param url URL to encode
|
||||
* @return URL, with path, query and ref encoded
|
||||
*/
|
||||
public static String urlToStringEncoded(URL url)
|
||||
{
|
||||
String s;
|
||||
String path = url.getPath() != null ? Stream.of(url.getPath().split("/"))
|
||||
.map(s2 -> URLEncoder.encode(s2, StandardCharsets.UTF_8)).collect(Collectors.joining("/")) : "";
|
||||
return url.getProtocol()
|
||||
+ ':'
|
||||
+ (((s = url.getAuthority()) != null && s.length() > 0) ? "//" + s : "")
|
||||
+ (path)
|
||||
+ (((s = url.getQuery()) != null) ? '?' + urlEncode(s) : "")
|
||||
+ (((s = url.getRef()) != null) ? '#' + urlEncode(s) : "");
|
||||
}
|
||||
|
||||
private static String urlEncode(String s)
|
||||
{
|
||||
return URLEncoder.encode(s, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user