Merge branch 'master' into externalserror1

This commit is contained in:
Tyler Bochard
2020-03-12 22:33:30 -04:00
committed by GitHub
96 changed files with 2094 additions and 2758 deletions

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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 "";
}
}

View File

@@ -31,7 +31,7 @@ import lombok.Value;
/**
* Represents Discord Rich Presence RPC data
*/
@Builder
@Builder(toBuilder = true)
@Value
public class DiscordPresence
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -1,6 +1,6 @@
package net.runelite.client.eventbus;
import io.reactivex.functions.Consumer;
import io.reactivex.rxjava3.functions.Consumer;
import lombok.Value;
@Value

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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()

View File

@@ -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;

View File

@@ -53,8 +53,6 @@ public @interface PluginDescriptor
*/
boolean hidden() default false;
boolean developerPlugin() default false;
boolean loadWhenOutdated() default false;
PluginType type() default PluginType.UNCATEGORIZED;

View File

@@ -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);
}

View File

@@ -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 ->

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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())
{

View File

@@ -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));
}
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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)

View File

@@ -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);
}
}