From d434e48d772781270b3b196936b59b26b5c97683 Mon Sep 17 00:00:00 2001 From: Anthony Alves Date: Fri, 22 May 2020 15:48:12 -0400 Subject: [PATCH] chat-history: add option to clear history for all chatbox tabs (#11543) Signed-off-by: Tomas Slusny Co-authored-by: Tomas Slusny --- .../java/net/runelite/api/ChatLineBuffer.java | 6 +- .../net/runelite/api/widgets/WidgetID.java | 6 + .../net/runelite/api/widgets/WidgetInfo.java | 6 + .../chathistory/ChatHistoryConfig.java | 11 ++ .../chathistory/ChatHistoryPlugin.java | 106 +++++++++++++++--- .../plugins/chathistory/ChatboxTab.java | 94 ++++++++++++++++ 6 files changed, 214 insertions(+), 15 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatboxTab.java diff --git a/runelite-api/src/main/java/net/runelite/api/ChatLineBuffer.java b/runelite-api/src/main/java/net/runelite/api/ChatLineBuffer.java index a1f8a7e056..94e26d6812 100644 --- a/runelite-api/src/main/java/net/runelite/api/ChatLineBuffer.java +++ b/runelite-api/src/main/java/net/runelite/api/ChatLineBuffer.java @@ -46,7 +46,11 @@ public interface ChatLineBuffer int getLength(); /** - * Removes a message node + * Removes a message node. + * + * This method modifies the underlying MessageNode array. If removing multiple MessageNodes at a time, + * clone the original {@link #getLines()} array; as items in the array will get modified and be left in an + * inconsistent state. * * @param node the {@link MessageNode} to remove */ 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 04f22f9312..a88b2e37c0 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 @@ -466,6 +466,12 @@ public class WidgetID { static final int PARENT = 0; static final int BUTTONS = 1; + static final int TAB_ALL = 4; + static final int TAB_GAME = 8; + static final int TAB_PUBLIC = 13; + static final int TAB_PRIVATE = 18; + static final int TAB_CLAN = 23; + static final int TAB_TRADE = 28; static final int REPORT_TEXT = 36; static final int FRAME = 37; static final int TRANSPARENT_BACKGROUND = 38; 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 eea24201ba..aeef3d01bf 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 @@ -365,6 +365,12 @@ public enum WidgetInfo 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), + CHATBOX_TAB_ALL(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TAB_ALL), + CHATBOX_TAB_GAME(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TAB_GAME), + CHATBOX_TAB_PUBLIC(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TAB_PUBLIC), + CHATBOX_TAB_PRIVATE(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TAB_PRIVATE), + CHATBOX_TAB_CLAN(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TAB_CLAN), + CHATBOX_TAB_TRADE(WidgetID.CHATBOX_GROUP_ID, WidgetID.Chatbox.TAB_TRADE), 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 cf558c3759..a552d39951 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 @@ -63,4 +63,15 @@ public interface ChatHistoryConfig extends Config { return true; } + + @ConfigItem( + keyName = "clearHistory", + name = "Clear history option for all tabs", + description = "Add 'Clear history' option chatbox tab buttons", + position = 3 + ) + default boolean clearHistory() + { + 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 7f4db12375..4ed0f21646 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2020, Anthony * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,6 +28,7 @@ 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.Color; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.KeyEvent; @@ -35,14 +37,17 @@ import java.util.Deque; import java.util.Iterator; import java.util.Queue; import javax.inject.Inject; +import net.runelite.api.ChatLineBuffer; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; +import net.runelite.api.MessageNode; 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.MenuEntryAdded; import net.runelite.api.events.MenuOpened; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.vars.InputType; @@ -59,6 +64,7 @@ import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.ColorUtil; import net.runelite.client.util.Text; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -72,7 +78,6 @@ 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; @@ -102,7 +107,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener { return configManager.getConfig(ChatHistoryConfig.class); } - + @Override protected void startUp() { @@ -247,20 +252,11 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { - String menuOption = event.getMenuOption(); + final String menuOption = event.getMenuOption(); - if (menuOption.contains(CLEAR_HISTORY)) + if (CLEAR_HISTORY.equals(menuOption)) { - if (menuOption.startsWith(CLEAR_PRIVATE)) - { - messageQueue.removeIf(e -> e.getType() == ChatMessageType.PRIVATECHAT || - e.getType() == ChatMessageType.PRIVATECHATOUT || e.getType() == ChatMessageType.MODPRIVATECHAT); - friends.clear(); - } - else - { - messageQueue.removeIf(e -> e.getType() == ChatMessageType.PUBLICCHAT || e.getType() == ChatMessageType.MODCHAT); - } + clearChatboxHistory(ChatboxTab.of(event.getWidgetId())); } else if (COPY_TO_CLIPBOARD.equals(menuOption) && !Strings.isNullOrEmpty(currentMessage)) { @@ -269,6 +265,88 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener } } + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded entry) + { + final String option = Text.removeTags(entry.getOption()); + final ChatboxTab tab = ChatboxTab.of(entry.getActionParam1()); + + if (!config.clearHistory() || tab == null || !option.equals(tab.getAfter())) + { + return; + } + + final MenuEntry clearEntry = new MenuEntry(); + clearEntry.setTarget(""); + clearEntry.setType(MenuAction.RUNELITE.getId()); + clearEntry.setParam0(entry.getActionParam0()); + clearEntry.setParam1(entry.getActionParam1()); + + if (tab == ChatboxTab.GAME) + { + // keep type as the original CC_OP to correctly group "Game: Clear history" with + // other tab "Game: *" options. + clearEntry.setType(entry.getType()); + } + + final StringBuilder messageBuilder = new StringBuilder(); + + if (tab != ChatboxTab.ALL) + { + messageBuilder.append(ColorUtil.wrapWithColorTag(tab.getName() + ": ", Color.YELLOW)); + } + + messageBuilder.append(CLEAR_HISTORY); + clearEntry.setOption(messageBuilder.toString()); + + final MenuEntry[] menuEntries = client.getMenuEntries(); + client.setMenuEntries(ArrayUtils.insert(menuEntries.length - 1, menuEntries, clearEntry)); + } + + private void clearMessageQueue(ChatboxTab tab) + { + if (tab == ChatboxTab.ALL || tab == ChatboxTab.PRIVATE) + { + friends.clear(); + } + + messageQueue.removeIf(e -> tab.getMessageTypes().contains(e.getType())); + } + + private void clearChatboxHistory(ChatboxTab tab) + { + if (tab == null) + { + return; + } + + boolean removed = false; + for (ChatMessageType msgType : tab.getMessageTypes()) + { + final ChatLineBuffer lineBuffer = client.getChatLineMap().get(msgType.getType()); + if (lineBuffer == null) + { + continue; + } + + final MessageNode[] lines = lineBuffer.getLines().clone(); + for (final MessageNode line : lines) + { + if (line != null) + { + lineBuffer.removeMessageNode(line); + removed = true; + } + } + } + + if (removed) + { + clientThread.invoke(() -> client.runScript(ScriptID.BUILD_CHATBOX)); + clearMessageQueue(tab); + } + } + /** * Small hack to prevent plugins checking for specific messages to match * @param message message diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatboxTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatboxTab.java new file mode 100644 index 0000000000..9411f08c82 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatboxTab.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020, Anthony + * 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.chathistory; + +import com.google.common.collect.ImmutableList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.ChatMessageType; +import net.runelite.api.widgets.WidgetInfo; + +@Getter +enum ChatboxTab +{ + + ALL("All", "Switch tab", WidgetInfo.CHATBOX_TAB_ALL, + ChatMessageType.values()), + + // null 'after' var since we're not adding to menu + PRIVATE("Private", null, WidgetInfo.CHATBOX_TAB_PRIVATE, + ChatMessageType.PRIVATECHAT, ChatMessageType.PRIVATECHATOUT, ChatMessageType.MODPRIVATECHAT, + ChatMessageType.LOGINLOGOUTNOTIFICATION), + + // null 'after' var since we're not adding to menu + PUBLIC("Public", null, WidgetInfo.CHATBOX_TAB_PUBLIC, + ChatMessageType.PUBLICCHAT, ChatMessageType.AUTOTYPER, ChatMessageType.MODCHAT, ChatMessageType.MODAUTOTYPER), + + GAME("Game", "Game: Filter", WidgetInfo.CHATBOX_TAB_GAME, + ChatMessageType.GAMEMESSAGE, ChatMessageType.ENGINE, ChatMessageType.BROADCAST, + ChatMessageType.SNAPSHOTFEEDBACK, ChatMessageType.ITEM_EXAMINE, ChatMessageType.NPC_EXAMINE, + ChatMessageType.OBJECT_EXAMINE, ChatMessageType.FRIENDNOTIFICATION, ChatMessageType.IGNORENOTIFICATION, + ChatMessageType.CONSOLE, ChatMessageType.SPAM, ChatMessageType.PLAYERRELATED, ChatMessageType.TENSECTIMEOUT, + ChatMessageType.WELCOME, ChatMessageType.UNKNOWN), + + CLAN("Clan", "Clan: Off", WidgetInfo.CHATBOX_TAB_CLAN, + ChatMessageType.FRIENDSCHATNOTIFICATION, ChatMessageType.FRIENDSCHAT, ChatMessageType.CHALREQ_FRIENDSCHAT), + + TRADE("Trade", "Trade: Off", WidgetInfo.CHATBOX_TAB_TRADE, + ChatMessageType.TRADE_SENT, ChatMessageType.TRADEREQ, ChatMessageType.TRADE, ChatMessageType.CHALREQ_TRADE), + ; + + private static final Map TAB_MESSAGE_TYPES = new HashMap<>(); + + @Nullable + private final String after; + private final String name; + private final int widgetId; + private final List messageTypes; + + ChatboxTab(String name, String after, WidgetInfo widgetId, ChatMessageType... messageTypes) + { + this.name = name; + this.after = after; + this.widgetId = widgetId.getId(); + this.messageTypes = ImmutableList.copyOf(messageTypes); + } + + static + { + for (ChatboxTab t : values()) + { + TAB_MESSAGE_TYPES.put(t.widgetId, t); + } + } + + static ChatboxTab of(int widgetId) + { + return TAB_MESSAGE_TYPES.get(widgetId); + } +}