diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
index d29593255e..e861861859 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
@@ -54,6 +54,8 @@ public class WidgetID
public static final int BA_COLLECTOR_GROUP_ID = 486;
public static final int BA_DEFENDER_GROUP_ID = 487;
public static final int BA_HEALER_GROUP_ID = 488;
+ public static final int LEVEL_UP_GROUP_ID = 233;
+ public static final int QUEST_COMPLETED_GROUP_ID = 277;
static class WorldMap
{
@@ -248,4 +250,14 @@ public class WidgetID
static final int ROLE_SPRITE = 6;
static final int ROLE = 7;
}
+
+ static class LevelUp
+ {
+ static final int TEXT = 1;
+ }
+
+ static class QuestCompleted
+ {
+ static final int NAME_TEXT = 2;
+ }
}
diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
index b73990fca2..7995627c8f 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
@@ -152,7 +152,13 @@ public enum WidgetInfo
BA_DEF_CALL_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
BA_DEF_LISTEN_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.CORRECT_STYLE),
BA_DEF_ROLE_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
- BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE);
+ BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),
+
+ LEVEL_UP(WidgetID.LEVEL_UP_GROUP_ID, 0),
+ LEVEL_UP_TEXT(WidgetID.LEVEL_UP_GROUP_ID, WidgetID.LevelUp.TEXT),
+
+ QUEST_COMPLETED(WidgetID.QUEST_COMPLETED_GROUP_ID, 0),
+ QUEST_COMPLETED_NAME_TEXT(WidgetID.QUEST_COMPLETED_GROUP_ID, WidgetID.QuestCompleted.NAME_TEXT);
private final int groupId;
private final int childId;
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index 78633aa483..80a21011a7 100644
--- a/runelite-client/pom.xml
+++ b/runelite-client/pom.xml
@@ -78,7 +78,7 @@
org.pushingpixels
substance
- 7.1.00-rc
+ 8.0.00-dev
org.pushingpixels
diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
index b4069ee4ab..7fde52249e 100644
--- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
@@ -39,6 +39,7 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
+import net.runelite.client.events.ClientUILoaded;
import net.runelite.client.account.SessionManager;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.config.ConfigManager;
@@ -55,6 +56,7 @@ public class RuneLite
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins");
+ public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
private static Injector injector;
private static OptionSet options;
@@ -164,6 +166,8 @@ public class RuneLite
pluginManager.watch();
SwingUtilities.invokeAndWait(() -> gui.showWithChrome(runeliteConfig.enableCustomChrome()));
+
+ eventBus.post(new ClientUILoaded());
}
public void setGui(ClientUI gui)
diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java
index 63e549811c..2ab5179f52 100644
--- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java
+++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java
@@ -112,6 +112,8 @@ public class Hooks
{
log.warn("Error during overlay rendering", ex);
}
+
+ renderer.provideScreenshot(image);
}
public static void drawRegion(Region region, int var1, int var2, int var3, int var4, int var5, int var6)
diff --git a/runelite-client/src/main/java/net/runelite/client/events/ClientUILoaded.java b/runelite-client/src/main/java/net/runelite/client/events/ClientUILoaded.java
new file mode 100644
index 0000000000..dcff604949
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/events/ClientUILoaded.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.events;
+
+import lombok.Data;
+
+@Data
+public class ClientUILoaded
+{
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java
new file mode 100644
index 0000000000..cbf5a00492
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018, UniquePassive
+ * 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.screenshot;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup(
+ keyName = "screenshot",
+ name = "Screenshot",
+ description = "Configuration for the Screenshot plugin"
+)
+public interface ScreenshotConfig extends Config
+{
+ @ConfigItem(
+ keyName = "includeFrame",
+ name = "Include Client Frame",
+ description = "Configures whether or not the client frame is included in screenshots",
+ position = 0
+ )
+ default boolean includeFrame()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "displayDate",
+ name = "Display Date",
+ description = "Configures whether or not the report button shows the date the screenshot was taken",
+ position = 1
+ )
+ default boolean displayDate()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "notifyWhenTaken",
+ name = "Notify When Taken",
+ description = "Configures whether or not you are notified when a screenshot has been taken",
+ position = 2
+ )
+ default boolean notifyWhenTaken()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "uploadScreenshot",
+ name = "Upload To Imgur",
+ description = "Configures whether or not screenshots are uploaded to Imgur and copied into your clipboard",
+ position = 3
+ )
+ default boolean uploadScreenshot()
+ {
+ return false;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java
new file mode 100644
index 0000000000..803c0f378b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2018, UniquePassive
+ * 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.screenshot;
+
+import net.runelite.client.plugins.screenshot.imgur.ImageUploadRequest;
+import net.runelite.client.plugins.screenshot.imgur.ImageUploadResponse;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Provides;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.io.InputStreamReader;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import net.runelite.api.Client;
+import net.runelite.client.events.ClientUILoaded;
+import net.runelite.api.events.WidgetHiddenChanged;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.Notifier;
+import net.runelite.client.RuneLite;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.ClientUI;
+import net.runelite.client.ui.FontManager;
+import net.runelite.client.ui.overlay.OverlayRenderer;
+import javax.imageio.ImageIO;
+import javax.inject.Inject;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.http.api.RuneLiteAPI;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import static net.runelite.api.widgets.WidgetID.LEVEL_UP_GROUP_ID;
+import static net.runelite.api.widgets.WidgetID.QUEST_COMPLETED_GROUP_ID;
+import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
+import okhttp3.HttpUrl;
+
+@PluginDescriptor(
+ name = "Screenshot plugin"
+)
+@Slf4j
+public class ScreenshotPlugin extends Plugin
+{
+ private static final String IMGUR_CLIENT_ID = "30d71e5f6860809";
+ private static final HttpUrl IMGUR_IMAGE_UPLOAD_URL = HttpUrl.parse("https://api.imgur.com/3/image");
+ private static final MediaType JSON = MediaType.parse("application/json");
+
+ private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM. dd, yyyy", Locale.US);
+ private static final DateFormat TIME_FORMAT = new SimpleDateFormat("h.m.s a d MMM. yyyy", Locale.US);
+
+ @Inject
+ ScreenshotConfig config;
+
+ @Inject
+ Notifier notifier;
+
+ @Inject
+ Client client;
+
+ @Inject
+ ClientUI clientUi;
+
+ @Inject
+ OverlayRenderer overlayRenderer;
+
+ private JButton titleBarButton;
+
+ @Provides
+ ScreenshotConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(ScreenshotConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ // prior to UI loading this does nothing
+ addButtonToTitleBar();
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ removeButtonFromTitlebar();
+ }
+
+ @Subscribe
+ public void clientUiLoaded(ClientUILoaded e)
+ {
+ addButtonToTitleBar();
+ }
+
+ private void addButtonToTitleBar()
+ {
+ try
+ {
+ BufferedImage iconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot.png"));
+ BufferedImage invertedIconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot_inverted.png"));
+
+ SwingUtilities.invokeLater(() ->
+ {
+ titleBarButton = new JButton();
+ titleBarButton.setToolTipText("Take screenshot");
+ titleBarButton.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseClicked(MouseEvent e)
+ {
+ super.mouseClicked(e);
+ takeScreenshot(TIME_FORMAT.format(new Date()), client.getLocalPlayer() != null);
+ }
+ });
+
+ clientUi.addButtonToTitleBar(titleBarButton, iconImage, invertedIconImage, 130);
+ });
+ }
+ catch (IOException ex)
+ {
+ log.warn("Error adding screenshot button to titlebar", ex);
+ }
+ }
+
+ private void removeButtonFromTitlebar()
+ {
+ SwingUtilities.invokeLater(() ->
+ {
+ JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(clientUi);
+ titleBar.remove(titleBarButton);
+
+ clientUi.revalidate();
+ clientUi.repaint();
+ });
+ }
+
+ @Subscribe
+ public void hideWidgets(WidgetHiddenChanged event)
+ {
+ Widget widget = event.getWidget();
+
+ if (widget.isHidden())
+ {
+ return;
+ }
+
+ String fileName;
+
+ switch (TO_GROUP(widget.getId()))
+ {
+ case LEVEL_UP_GROUP_ID:
+ {
+ Widget textChild = client.getWidget(WidgetInfo.LEVEL_UP_TEXT);
+
+ if (textChild == null)
+ {
+ return;
+ }
+
+ // "Your Firemaking level is now 9."
+ String text = textChild.getText();
+
+ String skillName = text.substring(5, text.indexOf(" level"));
+ String skillLevel = text.substring(text.lastIndexOf(" ") + 1, text.length() - 1);
+
+ fileName = skillName + " (" + skillLevel + ")";
+ break;
+ }
+ case QUEST_COMPLETED_GROUP_ID:
+ {
+ Widget textChild = client.getWidget(WidgetInfo.QUEST_COMPLETED_NAME_TEXT);
+
+ if (textChild == null)
+ {
+ return;
+ }
+
+ // "You have completed The Corsair Curse!"
+ String text = textChild.getText();
+
+ fileName = text.substring(19, text.length() - 1);
+ break;
+ }
+ default:
+ return;
+ }
+
+ takeScreenshot(fileName, config.displayDate());
+ }
+
+ private void takeScreenshot(String fileName, boolean displayDate)
+ {
+ overlayRenderer.requestScreenshot(image ->
+ {
+ BufferedImage screenshot = config.includeFrame()
+ ? new BufferedImage(clientUi.getWidth(), clientUi.getHeight(), BufferedImage.TYPE_INT_ARGB)
+ : new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+ Graphics graphics = screenshot.getGraphics();
+
+ int gameOffsetX = 0;
+ int gameOffsetY = 0;
+
+ if (config.includeFrame())
+ {
+ // Draw the client frame onto the screenshot
+ clientUi.paint(graphics);
+
+ // Evaluate the position of the game inside the frame
+ Point gamePoint = SwingUtilities.convertPoint(client.getCanvas(), 0, 0, clientUi);
+ gameOffsetX = gamePoint.x;
+ gameOffsetY = gamePoint.y;
+ }
+
+ // Draw the game onto the screenshot
+ graphics.drawImage(image, gameOffsetX, gameOffsetY, null);
+
+ if (displayDate)
+ {
+ try (InputStream reportButton = ScreenshotPlugin.class.getResourceAsStream("report_button.png"))
+ {
+ BufferedImage reportButtonImage = ImageIO.read(reportButton);
+
+ int x = gameOffsetX + 403;
+ int y = gameOffsetY + image.getHeight() - reportButtonImage.getHeight() - 1;
+
+ graphics.drawImage(reportButtonImage, x, y, null);
+
+ graphics.setFont(FontManager.getRunescapeSmallFont());
+ FontMetrics fontMetrics = graphics.getFontMetrics();
+
+ String date = DATE_FORMAT.format(new Date());
+ int dateWidth = fontMetrics.stringWidth(date);
+ int dateHeight = fontMetrics.getHeight();
+
+ int textX = x + reportButtonImage.getWidth() / 2 - dateWidth / 2;
+ int textY = y + reportButtonImage.getHeight() / 2 + dateHeight / 2;
+
+ graphics.setColor(Color.BLACK);
+ graphics.drawString(date, textX + 1, textY + 1);
+
+ graphics.setColor(Color.WHITE);
+ graphics.drawString(date, textX, textY);
+ }
+ catch (Exception ex)
+ {
+ log.warn("error displaying date on screenshot", ex);
+ }
+ }
+
+ File playerFolder = RuneLite.SCREENSHOT_DIR;
+
+ if (client.getLocalPlayer() != null)
+ {
+ playerFolder = new File(RuneLite.SCREENSHOT_DIR, client.getLocalPlayer().getName());
+ }
+
+ playerFolder.mkdirs();
+
+ try
+ {
+ File screenshotFile = new File(playerFolder, fileName + ".png");
+
+ ImageIO.write(screenshot, "PNG", screenshotFile);
+
+ if (config.uploadScreenshot())
+ {
+ uploadScreenshot(screenshotFile);
+ }
+ else if (config.notifyWhenTaken())
+ {
+ notifier.notify("A screenshot was saved to " + screenshotFile, TrayIcon.MessageType.INFO);
+ }
+ }
+ catch (IOException ex)
+ {
+ log.warn("error writing screenshot", ex);
+ }
+ });
+ }
+
+ private void uploadScreenshot(File screenshotFile) throws IOException
+ {
+ String json = RuneLiteAPI.GSON.toJson(new ImageUploadRequest(screenshotFile));
+
+ Request request = new Request.Builder()
+ .url(IMGUR_IMAGE_UPLOAD_URL)
+ .addHeader("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
+ .post(RequestBody.create(JSON, json))
+ .build();
+
+ RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
+ {
+ @Override
+ public void onFailure(Call call, IOException ex)
+ {
+ log.warn("error uploading screenshot", ex);
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException
+ {
+ InputStream in = response.body().byteStream();
+ ImageUploadResponse imageUploadResponse = RuneLiteAPI.GSON
+ .fromJson(new InputStreamReader(in), ImageUploadResponse.class);
+
+ if (imageUploadResponse.isSuccess())
+ {
+ String link = imageUploadResponse.getData().getLink();
+
+ StringSelection selection = new StringSelection(link);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(selection, selection);
+
+ if (config.notifyWhenTaken())
+ {
+ notifier.notify("A screenshot was uploaded and inserted into your clipboard!", TrayIcon.MessageType.INFO);
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/imgur/ImageUploadRequest.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/imgur/ImageUploadRequest.java
new file mode 100644
index 0000000000..91907b9158
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/imgur/ImageUploadRequest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018, UniquePassive
+ * 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.screenshot.imgur;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Base64;
+import lombok.Data;
+
+@Data
+public class ImageUploadRequest
+{
+ private final String image;
+ private final String type;
+
+ public ImageUploadRequest(File imageFile) throws IOException
+ {
+ this.image = Base64.getEncoder().encodeToString(Files.readAllBytes(imageFile.toPath()));
+ this.type = "base64";
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/imgur/ImageUploadResponse.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/imgur/ImageUploadResponse.java
new file mode 100644
index 0000000000..933fa86521
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/imgur/ImageUploadResponse.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018, UniquePassive
+ * 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.screenshot.imgur;
+
+import lombok.Data;
+
+@Data
+public class ImageUploadResponse
+{
+ private Data data;
+ private boolean success;
+
+ @lombok.Data
+ public static class Data
+ {
+ private String link;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
index 7756517b04..8e2f467351 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
@@ -30,19 +30,28 @@ import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Cursor;
+import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Frame;
+import java.awt.Image;
import java.awt.SystemTray;
import java.awt.TrayIcon;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.util.Enumeration;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
@@ -59,13 +68,16 @@ import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.client.RuneLiteProperties;
import org.pushingpixels.substance.api.skin.SubstanceGraphiteLookAndFeel;
-import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
+import org.pushingpixels.substance.internal.SubstanceSynapse;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities;
@Slf4j
public class ClientUI extends JFrame
{
private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH;
private static final BufferedImage ICON;
+ private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
@Getter
private TrayIcon trayIcon;
@@ -150,7 +162,36 @@ public class ClientUI extends JFrame
setLocationRelativeTo(getOwner());
if (customChrome)
{
- new TitleBarPane(this.getRootPane(), (SubstanceRootPaneUI) this.getRootPane().getUI()).editTitleBar(this);
+ try
+ {
+ BufferedImage discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png"));
+ BufferedImage invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png"));
+
+ JButton discordButton = new JButton();
+ discordButton.setToolTipText("Join Discord");
+ discordButton.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseClicked(MouseEvent e)
+ {
+ super.mouseClicked(e);
+ try
+ {
+ Desktop.getDesktop().browse(new URL(DISCORD_INVITE).toURI());
+ }
+ catch (IOException | URISyntaxException ex)
+ {
+ log.warn("error opening browser", ex);
+ }
+ }
+ });
+
+ addButtonToTitleBar(discordButton, discordIcon, invertedIcon, 100);
+ }
+ catch (IOException ex)
+ {
+ log.warn("unable to load discord button", ex);
+ }
}
setVisible(true);
@@ -172,6 +213,44 @@ public class ClientUI extends JFrame
}
}
+ public void addButtonToTitleBar(JButton button, Image iconImage, Image invertedIconImage, int xOffset)
+ {
+ JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(this);
+
+ if (titleBar == null)
+ {
+ return;
+ }
+
+ int size = titleBar.getHeight() - 6;
+
+ ImageIcon icon = new ImageIcon(iconImage.getScaledInstance(size, size, Image.SCALE_SMOOTH));
+ ImageIcon invertedIcon = new ImageIcon(invertedIconImage.getScaledInstance(size, size, Image.SCALE_SMOOTH));
+
+ button.setIcon(icon);
+ button.setRolloverIcon(invertedIcon);
+ button.putClientProperty(SubstanceSynapse.FLAT_LOOK, Boolean.TRUE);
+ button.putClientProperty(SubstanceTitlePaneUtilities.EXTRA_COMPONENT_KIND, SubstanceTitlePaneUtilities.ExtraComponentKind.TRAILING);
+ button.setFocusable(false);
+ button.setBounds(titleBar.getWidth() - xOffset, 2,
+ icon.getIconWidth() + 4, icon.getIconHeight() + 2);
+
+ titleBar.addComponentListener(new ComponentAdapter()
+ {
+ @Override
+ public void componentResized(ComponentEvent e)
+ {
+ super.componentResized(e);
+ button.setBounds(titleBar.getWidth() - xOffset, 1, button.getWidth(), button.getHeight());
+ }
+ });
+
+ titleBar.add(button);
+
+ revalidate();
+ repaint();
+ }
+
private static void setUIFont(FontUIResource f)
{
final Enumeration keys = UIManager.getDefaults().keys();
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/TitleBarPane.java b/runelite-client/src/main/java/net/runelite/client/ui/TitleBarPane.java
deleted file mode 100644
index c4002f9a20..0000000000
--- a/runelite-client/src/main/java/net/runelite/client/ui/TitleBarPane.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (c) 2017. l2-
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package net.runelite.client.ui;
-
-import java.awt.Desktop;
-import java.awt.Image;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import javax.imageio.ImageIO;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JFrame;
-import javax.swing.JRootPane;
-import lombok.extern.slf4j.Slf4j;
-import org.pushingpixels.substance.api.SubstanceLookAndFeel;
-import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
-import org.pushingpixels.substance.internal.utils.SubstanceTitlePane;
-
-@Slf4j
-public class TitleBarPane extends SubstanceTitlePane
-{
- private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
-
- private BufferedImage discordIcon;
- private BufferedImage invertedIcon;
-
- public TitleBarPane(JRootPane root, SubstanceRootPaneUI ui)
- {
- super(root, ui);
-
- try
- {
- discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png"));
- invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png"));
- }
- catch (IOException ex)
- {
- log.warn("unable to load discord icons", ex);
- }
- }
-
- public void editTitleBar(JFrame frame)
- {
- if (discordIcon == null || invertedIcon == null)
- {
- return;
- }
-
- JComponent titleBar = SubstanceLookAndFeel.getTitlePaneComponent(frame);
-
- JButton discordButton = new JButton();
- int heigth = titleBar.getHeight() - 3;
- int width = heigth;
- int x = titleBar.getWidth() - 100;
- int y = 1;
- ImageIcon icon = new ImageIcon(discordIcon.getScaledInstance(width - 3, heigth - 3, Image.SCALE_SMOOTH));
- ImageIcon invIcon = new ImageIcon(invertedIcon.getScaledInstance(width - 3, heigth - 3, Image.SCALE_SMOOTH));
-
- discordButton.setIcon(icon);
- discordButton.setRolloverIcon(invIcon);
- discordButton.putClientProperty("substancelaf.componentFlat", Boolean.TRUE);
- discordButton.putClientProperty("substancelaf.internal.titlePane.extraComponentKind", ExtraComponentKind.TRAILING);
- discordButton.setFocusable(false);
- discordButton.setBounds(x, y, icon.getIconWidth() + 4, icon.getIconHeight() + 2);
- discordButton.setToolTipText("Join Discord");
-
- discordButton.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mouseClicked(MouseEvent e)
- {
- super.mouseClicked(e);
- try
- {
- Desktop.getDesktop().browse(new URL(DISCORD_INVITE).toURI());
- }
- catch (IOException | URISyntaxException ex)
- {
- log.warn("error opening browser", ex);
- }
- }
- });
-
- titleBar.addComponentListener(new ComponentAdapter()
- {
- @Override
- public void componentResized(ComponentEvent e)
- {
- super.componentResized(e);
- discordButton.setBounds(titleBar.getWidth() - 100, y, discordButton.getWidth(), discordButton.getHeight());
- }
- });
-
- titleBar.add(discordButton);
- }
-}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java
index ae5d627b1a..a982962fca 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java
@@ -34,11 +34,14 @@ import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
+import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.widgets.Widget;
@@ -51,6 +54,7 @@ import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
@Singleton
+@Slf4j
public class OverlayRenderer
{
private static final int BORDER_TOP = 25;
@@ -75,6 +79,8 @@ public class OverlayRenderer
private BufferedImage surface;
private Graphics2D surfaceGraphics;
+ private ConcurrentLinkedQueue> screenshotRequests = new ConcurrentLinkedQueue<>();
+
@Subscribe
public void onResizableChanged(ResizeableChanged event)
{
@@ -299,4 +305,25 @@ public class OverlayRenderer
subGraphics.dispose();
return dimension;
}
+
+ public void provideScreenshot(BufferedImage image)
+ {
+ Consumer consumer;
+ while ((consumer = screenshotRequests.poll()) != null)
+ {
+ try
+ {
+ consumer.accept(image);
+ }
+ catch (Exception ex)
+ {
+ log.warn("error in screenshot callback", ex);
+ }
+ }
+ }
+
+ public void requestScreenshot(Consumer consumer)
+ {
+ screenshotRequests.add(consumer);
+ }
}
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/report_button.png b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/report_button.png
new file mode 100644
index 0000000000..34603643c6
Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/report_button.png differ
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png
new file mode 100644
index 0000000000..9a4f892bd7
Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot.png differ
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot_inverted.png b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot_inverted.png
new file mode 100644
index 0000000000..37a546d555
Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/screenshot/screenshot_inverted.png differ