From 84885f754688368b5b41988760564be526a3e10f Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Tue, 21 Apr 2020 22:32:08 +0200 Subject: [PATCH] Add option to copy chat message contents to clipboard Signed-off-by: Tomas Slusny --- .../net/runelite/api/widgets/WidgetID.java | 2 + .../net/runelite/api/widgets/WidgetInfo.java | 2 + .../chathistory/ChatHistoryConfig.java | 11 +++ .../chathistory/ChatHistoryPlugin.java | 82 +++++++++++++++++++ 4 files changed, 97 insertions(+) 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 79a9ee8765..8b93e7a4dc 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 @@ -475,6 +475,8 @@ public class WidgetID static final int MESSAGES = 55; static final int TRANSPARENT_BACKGROUND_LINES = 56; static final int INPUT = 57; + static final int MESSAGE_LINES = 58; + static final int FIRST_MESSAGE = 59; } static class Prayer 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 b54e93c5e4..59cb4f4160 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 @@ -362,6 +362,8 @@ public enum WidgetInfo CHATBOX_INPUT(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.INPUT), CHATBOX_TRANSPARENT_BACKGROUND(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TRANSPARENT_BACKGROUND), CHATBOX_TRANSPARENT_LINES(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TRANSPARENT_BACKGROUND_LINES), + CHATBOX_MESSAGE_LINES(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.MESSAGE_LINES), + CHATBOX_FIRST_MESSAGE(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.FIRST_MESSAGE), BA_HEAL_WAVE_TEXT(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE), BA_HEAL_CALL_TEXT(WidgetID.BA_HEALER_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java index e30629c742..cf558c3759 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java @@ -52,4 +52,15 @@ public interface ChatHistoryConfig extends Config { return true; } + + @ConfigItem( + keyName = "copyToClipboard", + name = "Copy to clipboard", + description = "Add option on chat messages to copy them to clipboard", + position = 2 + ) + default boolean copyToClipboard() + { + return true; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java index 77fbdd957f..bd0a95747d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java @@ -24,8 +24,11 @@ */ package net.runelite.client.plugins.chathistory; +import com.google.common.base.Strings; import com.google.common.collect.EvictingQueue; import com.google.inject.Provides; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; import java.awt.event.KeyEvent; import java.util.ArrayDeque; import java.util.Deque; @@ -34,12 +37,19 @@ import java.util.Queue; import javax.inject.Inject; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; import net.runelite.api.ScriptID; import net.runelite.api.VarClientInt; import net.runelite.api.VarClientStr; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.MenuOpened; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.vars.InputType; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; +import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; import net.runelite.client.callback.ClientThread; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; @@ -50,6 +60,7 @@ import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.util.Text; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @PluginDescriptor( @@ -62,12 +73,15 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape"; private static final String CLEAR_HISTORY = "Clear history"; private static final String CLEAR_PRIVATE = "Private:"; + private static final String COPY_TO_CLIPBOARD = "Copy to clipboard"; private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB; private static final int FRIENDS_MAX_SIZE = 5; private Queue messageQueue; private Deque friends; + private String currentMessage = null; + @Inject private Client client; @@ -104,6 +118,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener messageQueue = null; friends.clear(); friends = null; + currentMessage = null; keyManager.unregisterKeyListener(this); } @@ -167,6 +182,68 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener } } + @Subscribe + public void onMenuOpened(MenuOpened event) + { + if (event.getMenuEntries().length < 2 || !config.copyToClipboard()) + { + return; + } + + // Use second entry as first one can be walk here with transparent chatbox + final MenuEntry entry = event.getMenuEntries()[event.getMenuEntries().length - 2]; + + if (entry.getType() != MenuAction.CC_OP_LOW_PRIORITY.getId()) + { + return; + } + + final int groupId = TO_GROUP(entry.getParam1()); + final int childId = TO_CHILD(entry.getParam1()); + + if (groupId != WidgetInfo.CHATBOX.getGroupId()) + { + return; + } + + final Widget widget = client.getWidget(groupId, childId); + final Widget parent = widget.getParent(); + + if (WidgetInfo.CHATBOX_MESSAGE_LINES.getId() != parent.getId()) + { + return; + } + + // Get child id of first chat message static child so we can substract this offset to link to dynamic child + // later + final int first = WidgetInfo.CHATBOX_FIRST_MESSAGE.getChildId(); + + // Convert current message static widget id to dynamic widget id of message node with message contents + // When message is right clicked, we are actually right clicking static widget that contains only sender. + // The actual message contents are stored in dynamic widgets that follow same order as static widgets. + // Every first dynamic widget is message sender and every second one is message contents. + final int dynamicChildId = (childId - first) * 2 + 1; + + // Extract and store message contents when menu is opened because dynamic children can change while right click + // menu is open and dynamicChildId will be outdated + final Widget messageContents = parent.getChild(dynamicChildId); + if (messageContents == null) + { + return; + } + + currentMessage = messageContents.getText(); + + final MenuEntry menuEntry = new MenuEntry(); + menuEntry.setOption(COPY_TO_CLIPBOARD); + menuEntry.setTarget(entry.getTarget()); + menuEntry.setType(MenuAction.RUNELITE.getId()); + menuEntry.setParam0(entry.getParam0()); + menuEntry.setParam1(entry.getParam1()); + menuEntry.setIdentifier(entry.getIdentifier()); + client.setMenuEntries(ArrayUtils.insert(1, client.getMenuEntries(), menuEntry)); + } + @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { @@ -185,6 +262,11 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener messageQueue.removeIf(e -> e.getType() == ChatMessageType.PUBLICCHAT || e.getType() == ChatMessageType.MODCHAT); } } + else if (COPY_TO_CLIPBOARD.equals(menuOption) && !Strings.isNullOrEmpty(currentMessage)) + { + final StringSelection stringSelection = new StringSelection(Text.removeTags(currentMessage)); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + } } /**