From a0b5e490cd959977cb6acb5f53d7692dca3f5552 Mon Sep 17 00:00:00 2001 From: raiyni Date: Thu, 4 Oct 2018 15:35:48 +0200 Subject: [PATCH] Add tag tabs to bank tags plugin Bring the ability to add tabs to your bank for quick access to search tags. Features: * Adding a tab * Selecting and deselecting a tab * Changing a tab's icon * Reordering tabs * Scrolling of tabs (With mouse wheel, ingame buttons, and dragging an item/tab over ingame buttons) * Saving scroll position * Dragging an item to a tab to tag the item * Tagging placeholders * Removing only a tab * Removing a tab and deleting all tags from said tab * Faster bank search when using tag tabs Closes #1205 Closes #4426 Supersedes / Closes #4862 Supersedes / Closes #3750 Can close #4082. Signed-off-by: Tomas Slusny Co-authored-by: Tomas Slusny --- .../plugins/banktags/BankTagsConfig.java | 62 ++ .../plugins/banktags/BankTagsPlugin.java | 237 +++--- .../client/plugins/banktags/TagManager.java | 112 +++ .../plugins/banktags/tabs/TabInterface.java | 792 ++++++++++++++++++ .../plugins/banktags/tabs/TabManager.java | 148 ++++ .../plugins/banktags/tabs/TabSprites.java | 86 ++ .../client/plugins/banktags/tabs/TagTab.java | 59 ++ .../plugins/banktags/tabs/down-arrow.png | Bin 0 -> 720 bytes .../plugins/banktags/tabs/incinerator.png | Bin 0 -> 3198 bytes .../client/plugins/banktags/tabs/new-tab.png | Bin 0 -> 943 bytes .../plugins/banktags/tabs/tag-tab-active.png | Bin 0 -> 759 bytes .../client/plugins/banktags/tabs/tag-tab.png | Bin 0 -> 824 bytes .../client/plugins/banktags/tabs/up-arrow.png | Bin 0 -> 716 bytes 13 files changed, 1397 insertions(+), 99 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/down-arrow.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/incinerator.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/new-tab.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/tag-tab-active.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/tag-tab.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/up-arrow.png diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java new file mode 100644 index 0000000000..07ed95dfdf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("banktags") +public interface BankTagsConfig extends Config +{ + @ConfigItem( + keyName = "useTabs", + name = "Use Tag Tabs", + description = "Enable the ability to add tabs to your bank which allow fast access to tags.", + position = 1 + ) + default boolean tabs() + { + return true; + } + + @ConfigItem( + keyName = "position", + name = "", + description = "", + hidden = true + ) + default int position() + { + return 0; + } + + @ConfigItem( + keyName = "position", + name = "", + description = "" + ) + void position(int idx); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java index 1abcd58f7c..3d46f59527 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java @@ -1,5 +1,7 @@ /* * Copyright (c) 2018, Adam + * Copyright (c) 2018, Ron Young + * Copyright (c) 2018, Tomas Slusny * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,9 +26,12 @@ */ package net.runelite.client.plugins.banktags; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.eventbus.Subscribe; +import com.google.inject.Provides; +import java.awt.event.MouseWheelEvent; import java.util.Arrays; -import java.util.List; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.InventoryID; @@ -35,86 +40,100 @@ import net.runelite.api.ItemComposition; import net.runelite.api.ItemContainer; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.DraggingWidgetChanged; +import net.runelite.api.events.GameTick; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ChatboxInputManager; import net.runelite.client.game.ItemManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.input.MouseWheelListener; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.banktags.tabs.TabInterface; +import net.runelite.client.plugins.banktags.tabs.TabSprites; @PluginDescriptor( name = "Bank Tags", description = "Enable tagging of bank items and searching of bank tags", tags = {"searching", "tagging"} ) -public class BankTagsPlugin extends Plugin +public class BankTagsPlugin extends Plugin implements MouseWheelListener { - private static final String CONFIG_GROUP = "banktags"; - - private static final String ITEM_KEY_PREFIX = "item_"; + public static final Splitter SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); + public static final Joiner JOINER = Joiner.on(",").skipNulls(); + public static final String CONFIG_GROUP = "banktags"; + public static final String TAG_SEARCH = "tag:"; + public static final String EDIT_TAGS_MENU_OPTION = "Edit-tags"; + public static final String ICON_SEARCH = "icon_"; private static final String SEARCH_BANK_INPUT_TEXT = "Show items whose names or tags contain the following text:
" + "(To show only tagged items, start your search with 'tag:')"; - private static final String SEARCH_BANK_INPUT_TEXT_FOUND = "Show items whose names or tags contain the following text: (%d found)
" + "(To show only tagged items, start your search with 'tag:')"; - private static final String TAG_SEARCH = "tag:"; - - private static final String EDIT_TAGS_MENU_OPTION = "Edit-tags"; - - @Inject - private Client client; - @Inject private ItemManager itemManager; @Inject - private ConfigManager configManager; + private Client client; + + @Inject + private ClientThread clientThread; @Inject private ChatboxInputManager chatboxInputManager; - private String getTags(int itemId) + @Inject + private MouseManager mouseManager; + + @Inject + private BankTagsConfig config; + + @Inject + private TagManager tagManager; + + @Inject + private TabInterface tabInterface; + + @Provides + BankTagsConfig getConfig(ConfigManager configManager) { - String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); - if (config == null) - { - return ""; - } - return config; + return configManager.getConfig(BankTagsConfig.class); } - private void setTags(int itemId, String tags) + @Override + public void startUp() { - if (tags == null || tags.isEmpty()) - { - configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); - } - else - { - configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, tags); - } + mouseManager.registerMouseWheelListener(this); + clientThread.invokeLater(tabInterface::init); + client.getSpriteOverrides().putAll(TabSprites.toMap(client)); } - private int getTagCount(int itemId) + @Override + public void shutDown() { - String tags = getTags(itemId); - if (tags.length() > 0) + mouseManager.unregisterMouseWheelListener(this); + clientThread.invokeLater(tabInterface::destroy); + + for (TabSprites value : TabSprites.values()) { - return tags.split(",").length; + client.getSpriteOverrides().remove(value.getSpriteId()); } - return 0; } @Subscribe - public void onScriptEvent(ScriptCallbackEvent event) + public void onScriptCallbackEvent(ScriptCallbackEvent event) { String eventName = event.getEventName(); @@ -141,29 +160,15 @@ public class BankTagsPlugin extends Plugin case "bankSearchFilter": int itemId = itemManager.canonicalize(intStack[intStackSize - 1]); String itemName = stringStack[stringStackSize - 2]; - String searchInput = stringStack[stringStackSize - 1]; + String search = stringStack[stringStackSize - 1]; - String tagsConfig = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); - if (tagsConfig == null || tagsConfig.length() == 0) - { - intStack[intStackSize - 2] = itemName.contains(searchInput) ? 1 : 0; - return; - } - - boolean tagSearch = searchInput.startsWith(TAG_SEARCH); - String search; + boolean tagSearch = search.startsWith(TAG_SEARCH); if (tagSearch) { - search = searchInput.substring(TAG_SEARCH.length()).trim(); - } - else - { - search = searchInput; + search = search.substring(TAG_SEARCH.length()).trim(); } - List tags = Arrays.asList(tagsConfig.toLowerCase().split(",")); - - if (tags.stream().anyMatch(tag -> tag.contains(search.toLowerCase()))) + if (tagManager.findTag(itemId, search)) { // return true intStack[intStackSize - 2] = 1; @@ -173,54 +178,42 @@ public class BankTagsPlugin extends Plugin intStack[intStackSize - 2] = itemName.contains(search) ? 1 : 0; } break; + case "getSearchingTagTab": + intStack[intStackSize - 1] = tabInterface.isActive() ? 1 : 0; + break; } } @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { - int widgetId = event.getActionParam1(); - if (widgetId != WidgetInfo.BANK_ITEM_CONTAINER.getId()) - { - return; - } - - int index = event.getActionParam0(); - if (index < 0) - { - return; - } - - // Examine is the only guaranteed menuop to be added - if (!"Examine".equals(event.getOption())) - { - return; - } - - Widget container = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - Widget item = container.getChild(index); - int itemID = itemManager.canonicalize(item.getItemId()); - - String text = EDIT_TAGS_MENU_OPTION; - - int tagCount = getTagCount(itemID); - if (tagCount > 0) - { - text += " (" + tagCount + ")"; - } - - MenuEntry editTags = new MenuEntry(); - editTags.setParam0(event.getActionParam0()); - editTags.setParam1(event.getActionParam1()); - editTags.setTarget(event.getTarget()); - editTags.setOption(text); - editTags.setType(MenuAction.RUNELITE.getId()); - editTags.setIdentifier(event.getIdentifier()); - MenuEntry[] entries = client.getMenuEntries(); - entries = Arrays.copyOf(entries, entries.length + 1); - entries[entries.length - 1] = editTags; - client.setMenuEntries(entries); + + if (event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getOption().equals("Examine")) + { + Widget container = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + Widget item = container.getChild(event.getActionParam0()); + int itemID = itemManager.canonicalize(item.getItemId()); + String text = EDIT_TAGS_MENU_OPTION; + int tagCount = tagManager.getTags(itemID).size(); + if (tagCount > 0) + { + text += " (" + tagCount + ")"; + } + MenuEntry editTags = new MenuEntry(); + editTags.setParam0(event.getActionParam0()); + editTags.setParam1(event.getActionParam1()); + editTags.setTarget(event.getTarget()); + editTags.setOption(text); + editTags.setType(MenuAction.RUNELITE.getId()); + editTags.setIdentifier(event.getIdentifier()); + entries = Arrays.copyOf(entries, entries.length + 1); + entries[entries.length - 1] = editTags; + client.setMenuEntries(entries); + } + + tabInterface.handleAdd(event); } @Subscribe @@ -249,11 +242,9 @@ public class BankTagsPlugin extends Plugin } int itemId = itemManager.canonicalize(item.getId()); - ItemComposition itemComposition = itemManager.getItemComposition(itemId); String itemName = itemComposition.getName(); - - String initialValue = getTags(itemId); + String initialValue = tagManager.getTagString(itemId); chatboxInputManager.openInputWindow(itemName + " tags:", initialValue, (newTags) -> { @@ -261,9 +252,57 @@ public class BankTagsPlugin extends Plugin { return; } - setTags(itemId, newTags); + + tagManager.setTagString(itemId, newTags); }); } + else + { + tabInterface.handleClick(event); + } } + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (configChanged.getGroup().equals("banktags") && configChanged.getKey().equals("useTabs")) + { + if (config.tabs()) + { + clientThread.invokeLater(tabInterface::init); + } + else + { + clientThread.invokeLater(tabInterface::destroy); + } + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + tabInterface.update(); + } + + @Subscribe + public void onDraggingWidgetChanged(DraggingWidgetChanged event) + { + tabInterface.handleDrag(event.isDraggingWidget()); + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (event.getGroupId() == WidgetID.BANK_GROUP_ID) + { + tabInterface.init(); + } + } + + @Override + public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event) + { + clientThread.invokeLater(() -> tabInterface.handleWheel(event)); + return event; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java new file mode 100644 index 0000000000..8aeeb38237 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import com.google.common.base.Strings; +import java.util.Collection; +import java.util.LinkedHashSet; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.client.config.ConfigManager; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.JOINER; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.SPLITTER; +import net.runelite.client.util.Text; + +@Singleton +public class TagManager +{ + private static final String ITEM_KEY_PREFIX = "item_"; + private final ConfigManager configManager; + + @Inject + private TagManager(final ConfigManager configManager) + { + this.configManager = configManager; + } + + public String getTagString(int itemId) + { + String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + if (config == null) + { + return ""; + } + + return config; + } + + Collection getTags(int itemId) + { + return new LinkedHashSet<>(SPLITTER.splitToList(getTagString(itemId).toLowerCase())); + } + + public void setTagString(int itemId, String tags) + { + if (Strings.isNullOrEmpty(tags)) + { + configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + } + else + { + configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, tags); + } + } + + public void addTag(int itemId, String tag) + { + final Collection tags = getTags(itemId); + if (tags.add(Text.standardize(tag))) + { + setTags(itemId, tags); + } + } + + private void setTags(int itemId, Collection tags) + { + setTagString(itemId, JOINER.join(tags)); + } + + boolean findTag(int itemId, String search) + { + return getTags(itemId).stream().anyMatch(tag -> tag.contains(Text.standardize(search))); + } + + public void removeTag(String tag) + { + final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; + configManager.getConfigurationKeys(prefix).forEach(item -> removeTag(Integer.parseInt(item.replace(prefix, "")), tag)); + } + + public void removeTag(int itemId, String tag) + { + final Collection tags = getTags(itemId); + if (tags.remove(Text.standardize(tag))) + { + setTags(itemId, tags); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java new file mode 100644 index 0000000000..7be53ded15 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import com.google.common.base.Strings; +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.event.MouseWheelEvent; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Point; +import net.runelite.api.ScriptID; +import net.runelite.api.SoundEffectID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarClientStr; +import net.runelite.api.Varbits; +import net.runelite.api.WidgetType; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.vars.InputType; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.ChatboxInputManager; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.banktags.BankTagsConfig; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_SEARCH; +import net.runelite.client.plugins.banktags.TagManager; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.Text; + +@Singleton +public class TabInterface +{ + private static final Color HILIGHT_COLOR = Color.decode("#ff9040"); + private static final String SCROLL_UP = "Scroll up"; + private static final String SCROLL_DOWN = "Scroll down"; + private static final String NEW_TAB = "New tag tab"; + private static final String REMOVE_TAB = "Delete tag tab"; + private static final String VIEW_TAB = "View tag tab"; + private static final String CHANGE_ICON = "Change icon"; + private static final String REMOVE_TAG = "Remove-tag"; + private static final int TAB_HEIGHT = 40; + private static final int TAB_WIDTH = 39; + private static final int BUTTON_HEIGHT = 20; + private static final int MARGIN = 1; + private static final int SCROLL_TICK = 500; + + // Widget indexes for searching + private static final int INNER_CONTAINER_IDX = 2; + private static final int SETTINGS_IDX = 4; + private static final int ITEM_CONTAINER_IDX = 7; + private static final int SCROLLBAR_IDX = 8; + private static final int BOTTOM_BAR_IDX = 9; + private static final int SEARCH_BUTTON_BACKGROUND_IDX = 15; + private static final int TITLE_BAR_IDX = 16; + private static final int ITEM_COUNT_IDX = 17; + private static final int TAB_BAR_IDX = 18; + private static final int INCINERATOR_IDX = 19; + private static final int INCINERATOR_CONFIRM_IDX = 20; + private static final int HIDDEN_WIDGET_IDX = 21; + + private final Client client; + private final ClientThread clientThread; + private final ItemManager itemManager; + private final ConfigManager configManager; + private final TagManager tagManager; + private final TabManager tabManager; + private final ChatboxInputManager chatboxInputManager; + private final BankTagsConfig config; + private final Rectangle bounds = new Rectangle(); + private final Rectangle canvasBounds = new Rectangle(); + + private TagTab activeTab; + private int maxTabs; + private int currentTabIndex; + private TagTab iconToSet = null; + private Instant startScroll = Instant.now(); + private Object[] widgetIds; + + @Getter + private Widget upButton; + + @Getter + private Widget downButton; + + @Getter + private Widget newTab; + + @Getter + private Widget parent; + + @Inject + private TabInterface( + final Client client, + final ClientThread clientThread, + final ItemManager itemManager, + final ConfigManager configManager, + final TagManager tagManager, + final TabManager tabManager, + final ChatboxInputManager chatboxInputManager, + final BankTagsConfig config) + { + this.client = client; + this.clientThread = clientThread; + this.itemManager = itemManager; + this.configManager = configManager; + this.tagManager = tagManager; + this.tabManager = tabManager; + this.chatboxInputManager = chatboxInputManager; + this.config = config; + } + + public boolean isActive() + { + return activeTab != null; + } + + public void init() + { + if (isHidden()) + { + return; + } + + Widget bankContainer = client.getWidget(WidgetInfo.BANK_CONTAINER); + widgetIds = bankContainer.getOnLoadListener(); + + currentTabIndex = config.position(); + parent = client.getWidget(WidgetInfo.BANK_CONTENT_CONTAINER); + + updateBounds(); + + upButton = createGraphic("", TabSprites.UP_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); + upButton.setAction(1, SCROLL_UP); + int clickmask = upButton.getClickMask(); + clickmask |= WidgetConfig.DRAG; + upButton.setClickMask(clickmask); + + downButton = createGraphic("", TabSprites.DOWN_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); + downButton.setAction(1, SCROLL_DOWN); + clickmask = downButton.getClickMask(); + clickmask |= WidgetConfig.DRAG; + downButton.setClickMask(clickmask); + + newTab = createGraphic("", TabSprites.NEW_TAB.getSpriteId(), -1, TAB_WIDTH, 39, bounds.x, 0, true); + newTab.setAction(1, NEW_TAB); + + tabManager.clear(); + tabManager.getAllTabs().forEach(this::loadTab); + activateTab(null); + scrollTab(0); + } + + public void destroy() + { + activeTab = null; + currentTabIndex = 0; + maxTabs = 0; + parent = null; + + if (upButton != null) + { + upButton.setHidden(true); + downButton.setHidden(true); + newTab.setHidden(true); + } + + tabManager.clear(); + } + + public void update() + { + if (isHidden()) + { + parent = null; + + // If bank window was just hidden, update last active tab position + if (currentTabIndex != config.position()) + { + config.position(currentTabIndex); + } + + return; + } + + String str = client.getVar(VarClientStr.INPUT_TEXT); + + if (Strings.isNullOrEmpty(str)) + { + str = ""; + } + + Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + if (bankTitle != null && !bankTitle.isHidden()) + { + str = bankTitle.getText().replaceFirst("Showing items: ", ""); + + if (str.startsWith("Tab ")) + { + str = ""; + } + } + + str = Text.standardize(str); + + if (str.startsWith("tag:")) + { + str = str.substring(4); + activateTab(tabManager.find(str)); + } + else + { + activateTab(null); + } + + updateBounds(); + scrollTab(0); + } + + public void handleWheel(final MouseWheelEvent event) + { + if (isHidden()) + { + return; + } + + if (canvasBounds.contains(event.getPoint())) + { + scrollTab(event.getWheelRotation()); + } + } + + public void handleAdd(MenuEntryAdded event) + { + if (isHidden()) + { + return; + } + + MenuEntry[] entries = client.getMenuEntries(); + MenuEntry entry = entries[entries.length - 1]; + + if (activeTab != null + && event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getOption().equals("Examine")) + { + MenuEntry removeTag = new MenuEntry(); + removeTag.setParam0(event.getActionParam0()); + removeTag.setParam1(event.getActionParam1()); + removeTag.setTarget(event.getTarget()); + removeTag.setOption(REMOVE_TAG + " (" + activeTab.getTag() + ")"); + removeTag.setType(MenuAction.RUNELITE.getId()); + removeTag.setIdentifier(event.getIdentifier()); + entries = Arrays.copyOf(entries, entries.length + 1); + entries[entries.length - 1] = removeTag; + client.setMenuEntries(entries); + } + else if (iconToSet != null && (entry.getOption().startsWith("Withdraw-") || entry.getOption().equals("Release"))) + { + // TODO: Do not replace every withdraw option with change icon option + entry.setOption(CHANGE_ICON + " (" + iconToSet.getTag() + ")"); + client.setMenuEntries(entries); + } + } + + public void handleClick(MenuOptionClicked event) + { + if (isHidden()) + { + return; + } + + if (iconToSet != null) + { + if (event.getMenuOption().startsWith(CHANGE_ICON)) + { + ItemComposition item = getItem(event.getActionParam()); + int itemId = itemManager.canonicalize(item.getId()); + iconToSet.setIconItemId(itemId); + iconToSet.getIcon().setItemId(itemId); + configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + iconToSet.getTag(), itemId + ""); + event.consume(); + } + + // Reset icon selection even when we do not clicked item with icon + iconToSet = null; + } + + if (activeTab != null + && event.getMenuOption().equals("Search") + && client.getWidget(WidgetInfo.BANK_SEARCH_BUTTON_BACKGROUND).getSpriteId() != SpriteID.EQUIPMENT_SLOT_SELECTED) + { + activateTab(null); + // This ensures that when clicking Search when tab is selected, the search input is opened rather + // than client trying to close it first + client.setVar(VarClientStr.INPUT_TEXT, ""); + client.setVar(VarClientInt.INPUT_TYPE, 0); + } + else if (activeTab != null + && event.getMenuOption().startsWith("View tab")) + { + activateTab(null); + } + else if (activeTab != null + && event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getMenuAction() == MenuAction.RUNELITE + && event.getMenuOption().startsWith(REMOVE_TAG)) + { + // Add "remove" menu entry to all items in bank while tab is selected + event.consume(); + final ItemComposition item = getItem(event.getActionParam()); + final int itemId = itemManager.canonicalize(item.getId()); + tagManager.removeTag(itemId, activeTab.getTag()); + doSearch(InputType.SEARCH, TAG_SEARCH + activeTab.getTag()); + } + else + { + switch (event.getMenuOption()) + { + case SCROLL_UP: + event.consume(); + scrollTab(-1); + break; + case SCROLL_DOWN: + event.consume(); + scrollTab(1); + break; + case CHANGE_ICON: + event.consume(); + iconToSet = tabManager.find(Text.removeTags(event.getMenuTarget())); + break; + case VIEW_TAB: + event.consume(); + client.setVarbitValue(client.getVarps(), Varbits.CURRENT_BANK_TAB.getId(), 0); + Widget[] children = parent.getDynamicChildren(); + Widget clicked = children[event.getActionParam()]; + + TagTab tab = tabManager.find(Text.removeTags(clicked.getName())); + + if (tab.equals(activeTab)) + { + resetSearch(); + + clientThread.invokeLater(() -> client.runScript(ScriptID.CLOSE_CHATBOX_INPUT)); + } + else + { + openTag(TAG_SEARCH + Text.removeTags(clicked.getName())); + } + + client.playSoundEffect(SoundEffectID.UI_BOOP); + + break; + case NEW_TAB: + event.consume(); + chatboxInputManager.openInputWindow("Tag Name", "", (tagName) -> + { + if (!Strings.isNullOrEmpty(tagName)) + { + loadTab(tagName); + tabManager.save(); + scrollTab(0); + } + }); + break; + case REMOVE_TAB: + event.consume(); + String target = Text.standardize(event.getMenuTarget()); + + // TODO: Replace this number input selection with actual in-game select input + chatboxInputManager.openInputWindow( + "1. Delete tab " + target + " and tag from all items
" + + "2. Delete tab " + target + "
" + + "3. Cancel", "", (response) -> + { + switch (response) + { + case "1": + tagManager.removeTag(target); + if (activeTab != null && activeTab.getTag().equals(target)) + { + resetSearch(); + } + case "2": + deleteTab(target); + break; + default: + break; + } + }); + break; + } + } + } + + public void handleDrag(boolean isDragging) + { + if (isHidden()) + { + return; + } + + Widget draggedOn = client.getDraggedOnWidget(); + Widget draggedWidget = client.getDraggedWidget(); + + if (!isDragging || draggedOn == null) + { + return; + } + + // is dragging widget and mouse button released + if (client.getMouseCurrentButton() == 0) + { + if (draggedWidget.getItemId() > 0 && draggedWidget.getId() != parent.getId()) + { + // Tag an item dragged on a tag tab + if (draggedOn.getId() == parent.getId()) + { + int itemId = itemManager.canonicalize(draggedWidget.getItemId()); + tagManager.addTag(itemId, draggedOn.getName()); + } + } + else if (parent.getId() == draggedOn.getId() && parent.getId() == draggedWidget.getId()) + { + // Reorder tag tabs + if (!Strings.isNullOrEmpty(draggedOn.getName())) + { + tabManager.move(draggedWidget.getName(), draggedOn.getName()); + tabManager.save(); + updateTabs(); + } + } + } + else if (draggedWidget.getItemId() > 0) + { + MenuEntry[] entries = client.getMenuEntries(); + + if (entries.length > 0) + { + MenuEntry entry = entries[entries.length - 1]; + + if (draggedWidget.getItemId() > 0 && entry.getOption().equals(VIEW_TAB) && draggedOn.getId() != draggedWidget.getId()) + { + entry.setOption(TAG_SEARCH + Text.removeTags(entry.getTarget())); + entry.setTarget(draggedWidget.getName()); + client.setMenuEntries(entries); + } + + if (entry.getOption().equals(SCROLL_UP)) + { + scrollTick(-1); + } + else if (entry.getOption().equals(SCROLL_DOWN)) + { + scrollTick(1); + } + } + } + } + + private void resetSearch() + { + doSearch(InputType.NONE, ""); + } + + private boolean isHidden() + { + Widget widget = client.getWidget(WidgetInfo.BANK_CONTAINER); + return !config.tabs() || widget == null || widget.isHidden(); + } + + private void loadTab(String tag) + { + TagTab tagTab = tabManager.load(tag); + + if (tagTab.getBackground() == null) + { + Widget btn = createGraphic(ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), TabSprites.TAB_BACKGROUND.getSpriteId(), -1, TAB_WIDTH, TAB_HEIGHT, bounds.x, 1, true); + btn.setAction(1, VIEW_TAB); + btn.setAction(2, CHANGE_ICON); + btn.setAction(3, REMOVE_TAB); + tagTab.setBackground(btn); + } + + if (tagTab.getIcon() == null) + { + Widget icon = createGraphic(ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), -1, tagTab.getIconItemId(), 36, 32, bounds.x + 3, 1, false); + int clickmask = icon.getClickMask(); + clickmask |= WidgetConfig.DRAG; + clickmask |= WidgetConfig.DRAG_ON; + icon.setClickMask(clickmask); + icon.setDragDeadTime(5); + icon.setDragDeadZone(5); + tagTab.setIcon(icon); + } + + tabManager.add(tagTab); + } + + private void deleteTab(String tag) + { + if (activeTab != null && activeTab.getTag().equals(tag)) + { + doSearch(InputType.SEARCH, ""); + } + + tabManager.remove(tag); + configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + tag); + tabManager.save(); + + updateBounds(); + scrollTab(0); + } + + private void scrollTick(int direction) + { + // This ensures that dragging on scroll buttons do not scrolls too fast + if (startScroll.until(Instant.now(), ChronoUnit.MILLIS) >= SCROLL_TICK) + { + startScroll = Instant.now(); + scrollTab(direction); + } + } + + private void scrollTab(int direction) + { + maxTabs = (bounds.height - BUTTON_HEIGHT * 2 - MARGIN * 2) / TAB_HEIGHT; + + // prevent running into the incinerator + while (bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT * 2 + MARGIN > bounds.y + bounds.height) + { + --maxTabs; + } + + if (currentTabIndex + direction >= tabManager.size() || currentTabIndex + direction < 0) + { + currentTabIndex = 0; + } + + if ((tabManager.size() - (currentTabIndex + direction) >= maxTabs) && (currentTabIndex + direction > -1)) + { + currentTabIndex += direction; + } + else if (maxTabs < tabManager.size() && tabManager.size() - (currentTabIndex + direction) < maxTabs) + { + // Edge case when only 1 tab displays instead of up to maxTabs when one is deleted at the end of the list + currentTabIndex += direction; + scrollTab(-1); + } + + updateTabs(); + } + + private void activateTab(TagTab tagTab) + { + if (activeTab != null && activeTab.equals(tagTab)) + { + return; + } + + if (activeTab != null) + { + Widget tab = activeTab.getBackground(); + tab.setSpriteId(TabSprites.TAB_BACKGROUND.getSpriteId()); + tab.revalidate(); + activeTab = null; + } + + if (tagTab != null) + { + Widget tab = tagTab.getBackground(); + tab.setSpriteId(TabSprites.TAB_BACKGROUND_ACTIVE.getSpriteId()); + tab.revalidate(); + activeTab = tagTab; + } + } + + private void updateBounds() + { + Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (itemContainer == null) + { + return; + } + + int height = itemContainer.getHeight(); + + // If player isn't using normal bank tabs + if (itemContainer.getRelativeY() == 0) + { + height -= (TAB_HEIGHT + MARGIN); + } + + bounds.setSize(TAB_WIDTH + MARGIN * 2, height); + bounds.setLocation(MARGIN, TAB_HEIGHT + MARGIN); + + Widget incinerator = client.getWidget(WidgetInfo.BANK_INCINERATOR); + + if (incinerator != null && !incinerator.isHidden()) + { + // This is the required way to move incinerator, don't change it! + incinerator.setOriginalHeight(39); + incinerator.setOriginalWidth(48); + incinerator.setRelativeY(itemContainer.getHeight()); + incinerator.revalidate(); + + Widget child = incinerator.getDynamicChildren()[0]; + child.setHeight(39); + child.setWidth(48); + child.setType(WidgetType.GRAPHIC); + child.setSpriteId(TabSprites.INCINERATOR.getSpriteId()); + + bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight()); + } + + if (upButton != null) + { + Point p = upButton.getCanvasLocation(); + canvasBounds.setBounds(p.getX(), p.getY() + BUTTON_HEIGHT, bounds.width, maxTabs * TAB_HEIGHT + maxTabs * MARGIN); + } + } + + private void updateTabs() + { + int y = bounds.y + MARGIN + BUTTON_HEIGHT; + + if (maxTabs >= tabManager.size()) + { + currentTabIndex = 0; + } + else + { + y -= (currentTabIndex * TAB_HEIGHT + currentTabIndex * MARGIN); + } + + for (TagTab tab : tabManager.getTabs()) + { + updateWidget(tab.getBackground(), y); + updateWidget(tab.getIcon(), y + 4); + + // Edge case where item icon is 1 pixel out of bounds + tab.getIcon().setHidden(tab.getBackground().isHidden()); + + // Keep item widget shown while drag scrolling + if (client.getDraggedWidget() == tab.getIcon()) + { + tab.getIcon().setHidden(false); + } + + y += TAB_HEIGHT + MARGIN; + } + + boolean hidden = !(tabManager.size() > 0); + + upButton.setHidden(hidden); + upButton.setOriginalY(bounds.y); + upButton.revalidate(); + + downButton.setHidden(hidden); + downButton.setOriginalY(bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT + MARGIN); + downButton.revalidate(); + } + + private Widget createGraphic(String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) + { + Widget widget = parent.createChild(-1, WidgetType.GRAPHIC); + widget.setOriginalWidth(width); + widget.setOriginalHeight(height); + widget.setOriginalX(x); + widget.setOriginalY(y); + + widget.setSpriteId(spriteId); + + if (itemId > -1) + { + widget.setItemId(itemId); + widget.setItemQuantity(-1); + widget.setBorderType(1); + } + + if (hasListener) + { + widget.setOnOpListener(ScriptID.NULL); + widget.setHasListener(true); + } + + widget.setName(name); + widget.revalidate(); + + return widget; + } + + private void updateWidget(Widget t, int y) + { + t.setOriginalY(y); + t.setRelativeY(y); + t.setHidden(y < (bounds.y + BUTTON_HEIGHT + MARGIN) || y > (bounds.y + bounds.height - TAB_HEIGHT - MARGIN - BUTTON_HEIGHT)); + t.revalidate(); + } + + private void doSearch(InputType inputType, String search) + { + // In case the widget ids array is incorrect, do not proceed + if (widgetIds == null || widgetIds.length < 21) + { + return; + } + + clientThread.invokeLater(() -> + { + // This ensures that any chatbox input (e.g from search) will not remain visible when + // selecting/changing tab + client.runScript(ScriptID.CLOSE_CHATBOX_INPUT); + + client.setVar(VarClientInt.INPUT_TYPE, inputType.getType()); + client.setVar(VarClientStr.INPUT_TEXT, search); + + client.runScript(ScriptID.BANK_LAYOUT, + WidgetInfo.BANK_CONTAINER.getId(), + widgetIds[INNER_CONTAINER_IDX], + widgetIds[SETTINGS_IDX], + widgetIds[ITEM_CONTAINER_IDX], + widgetIds[SCROLLBAR_IDX], + widgetIds[BOTTOM_BAR_IDX], + widgetIds[TITLE_BAR_IDX], + widgetIds[ITEM_COUNT_IDX], + widgetIds[SEARCH_BUTTON_BACKGROUND_IDX], + widgetIds[TAB_BAR_IDX], + widgetIds[INCINERATOR_IDX], + widgetIds[INCINERATOR_CONFIRM_IDX], + widgetIds[HIDDEN_WIDGET_IDX]); + }); + } + + private ItemComposition getItem(int idx) + { + ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); + Item item = bankContainer.getItems()[idx]; + return itemManager.getItemComposition(item.getId()); + } + + private void openTag(String tag) + { + doSearch(InputType.SEARCH, tag); + activateTab(tabManager.find(tag.substring(4))); + + // When tab is selected with search window open, the search window closes but the search button + // stays highlighted, this solves that issue + Widget searchBackground = client.getWidget(WidgetInfo.BANK_SEARCH_BUTTON_BACKGROUND); + searchBackground.setSpriteId(SpriteID.EQUIPMENT_SLOT_TILE); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java new file mode 100644 index 0000000000..dd4308af04 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import com.google.common.base.MoreObjects; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import net.runelite.api.ItemID; +import net.runelite.client.config.ConfigManager; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.JOINER; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.SPLITTER; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.math.NumberUtils; + +@Singleton +class TabManager +{ + private static final String TAG_TABS_CONFIG = "tagtabs"; + + @Getter + private final List tabs = new ArrayList<>(); + private final ConfigManager configManager; + + @Inject + private TabManager(ConfigManager configManager) + { + this.configManager = configManager; + } + + void add(TagTab tagTab) + { + if (!contains(tagTab.getTag())) + { + tabs.add(tagTab); + } + } + + void clear() + { + tabs.forEach(t -> t.setHidden(true)); + tabs.clear(); + } + + TagTab find(String tag) + { + Optional first = tabs.stream().filter(t -> t.getTag().equals(Text.standardize(tag))).findAny(); + return first.orElse(null); + } + + List getAllTabs() + { + return SPLITTER.splitToList(MoreObjects.firstNonNull(configManager.getConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG), "")); + } + + TagTab load(String tag) + { + TagTab tagTab = find(tag); + + if (tagTab == null) + { + tag = Text.standardize(tag); + String item = configManager.getConfiguration(CONFIG_GROUP, ICON_SEARCH + tag); + int itemid = NumberUtils.toInt(item, ItemID.SPADE); + tagTab = new TagTab(itemid, tag); + } + + return tagTab; + } + + void move(String tagToMove, String tagDestination) + { + tagToMove = Text.standardize(tagToMove); + tagDestination = Text.standardize(tagDestination); + + if (contains(tagToMove) && contains(tagDestination)) + { + Collections.swap(tabs, indexOf(tagToMove), indexOf(tagDestination)); + } + } + + void remove(String tag) + { + TagTab tagTab = find(tag); + + if (tagTab != null) + { + tagTab.setHidden(true); + tabs.remove(tagTab); + } + } + + void save() + { + String tags = JOINER.join(tabs.stream().map(TagTab::getTag).collect(Collectors.toList())); + configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags); + } + + int size() + { + return tabs.size(); + } + + private boolean contains(String tag) + { + return tabs.stream().anyMatch(t -> t.getTag().equals(tag)); + } + + private int indexOf(TagTab tagTab) + { + return tabs.indexOf(tagTab); + } + + private int indexOf(String tag) + { + return indexOf(find(tag)); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java new file mode 100644 index 0000000000..290db8c67c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import java.awt.image.BufferedImage; +import java.awt.image.PixelGrabber; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.SpritePixels; +import net.runelite.client.util.ImageUtil; + +@Slf4j +public enum TabSprites +{ + INCINERATOR(-200, "incinerator.png"), + TAB_BACKGROUND(-201, "tag-tab.png"), + TAB_BACKGROUND_ACTIVE(-202, "tag-tab-active.png"), + UP_ARROW(-203, "up-arrow.png"), + DOWN_ARROW(-204, "down-arrow.png"), + NEW_TAB(-205, "new-tab.png"); + + @Getter + private final int spriteId; + private final BufferedImage image; + + TabSprites(final int spriteId, final String imageName) + { + this.spriteId = spriteId; + this.image = ImageUtil.getResourceStreamFromClass(this.getClass(), imageName); + } + + public static Map toMap(Client client) + { + final Map map = new HashMap<>(); + + for (TabSprites value : values()) + { + map.put(value.spriteId, getSpritePixels(client, value.image)); + } + + return map; + } + + private static SpritePixels getSpritePixels(Client client, BufferedImage image) + { + int[] pixels = new int[image.getWidth() * image.getHeight()]; + + try + { + new PixelGrabber(image, 0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()) + .grabPixels(); + } + catch (InterruptedException ex) + { + log.debug("PixelGrabber was interrupted: ", ex); + } + + return client.createSpritePixels(pixels, image.getWidth(), image.getHeight()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java new file mode 100644 index 0000000000..d3d417f465 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.runelite.api.widgets.Widget; + +@Data +@EqualsAndHashCode(of = "tag") +class TagTab +{ + private final String tag; + private int iconItemId; + private Widget background; + private Widget icon; + + TagTab(int iconItemId, String tag) + { + this.iconItemId = iconItemId; + this.tag = tag; + } + + void setHidden(boolean hide) + { + if (background != null) + { + background.setHidden(hide); + } + + if (icon != null) + { + icon.setHidden(hide); + } + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/down-arrow.png b/runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/down-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..b9f98803c0e7061f8c2c8709d048dac586bd6f00 GIT binary patch literal 720 zcmV;>0x$iEP)#h6V~0n_ z08Ai&efV_ppg_?I5f1kEI6OKIaM7$XR0Tu;kox5VL6>?`RqBtOYGC1*4-rA@eE=%6 z-nW{Mgw%)Euf#_?=iu(RSy-q-p^CI;Yrq)HXvf^5BSeB~sWr+KB1raW4w!D1s$dS{ zh6sLLs&&qN(m=Tc?N)! z^K{KoLazL|zG3;}SEhmk=8lSzX=qq-%z@zJs-J+QAv3bBJj>pO&ny?>PCk z#1mN@Fmt|~E?M2&F`G_NkzU?9j^P0?KBI8SWH$^=^49}%XIf5pySqc>&eyYxrrIhq zPQEWO;LYnD$}-EG?DRSkpFZ4wn$s4^tempD{Tx;0^!$?ByT6>CUs4nWuU>93n?4yX z)Qmv-Q5%Dc6_#3P54nQ~xtNt~Z*4M}6s)dqnM?{^JlkMCE8B`!p`RypSQG0OVNY|F z(Cd7tT1RYSF~f0wU){2`vEb=q)*?8#8PatZ$L6ET4JL%w?IQItxXv9HP=w9JoXy32 zxCM2B5&C7l!w6U(F7~I0W?#5c0 z(h_eEXsR7|)CH@1=Re`9$|yv#&4gGGN>@LN?IqpfDC3`J89q|P*;Ue z9MHDk_2v#R7DJ>N#u&_4W`-FN`Qm<-jEc!pw=CJW zo4v86t07DFvW&S!WGT{uh;MXzy5IMF&vV{$&iniS-rxU!e*g2H=Q;6qXD#*=g@5%!JK{=umhKjMZfqMm#bg6vU4MfXOL(5Z22d zPX*!d1R@y$UShX^K}2r^*u}sWY8zyR_a$0|hu|H<&pLU9`+J@62A?`9VM2%V1xR=* z7DOinkSTCF0{mSU&cEM*Az;vV3e_J0#@O0{%mPF3AOjr(9Vqyu1jr=B+XwDoZt-I= ze}(}2QmH|32qY{lOeaiFCoqHnft@&U0s_^A=;~_o8QK&EnTn-rlPO1bB>vVg$5XsQ zh(T0hAQ`lyiNyuds0c8a9|!#q$J2>ENX$=tFJcf@$1e!714CdsP{@CCsYIWDDj`#T zSj9IDvJ?3~I+n(N>>qXi5BsM$pYeBszpH>-Vo4+{0t|ye48G_60o&lo6x%>=e87*= zce24zBrE~Xj}WN72(TgSJO7`63(W2IvkaHaw1F=g~jj_*~UXi{@pFpa9y>gIU#{EQ zIoMiXBCw!OA$mr!(DT@*()#C}yVi`ROUki<%Hh+Ek?W#o-xJ33Md@1nO(`B^XBu{|f?AMeJl1>S>vjVjVUX2wL@xKds&E$^08_k$| z_kOOt{8gWa0awp!X>6~wNs+<%VaMU6?fItd%jacJo>?C#a?Hi?#wUb8wvGs&@laxK z;ABKqh!L-`q!aLDe4XouCZN5V9^W4d)SscU-C*dY{Z?u%muK&e56mSCM10O5SOAK@ z7jVLx4%^j+RP}50_}92Sx~qzsQvLtgkG)Qn%-?20cP`mU~m^p6JKd+La@x zUpZ`dWSc#TWW0ISO+!BJ0m!B0tglN^pY~}jH5omqXpni3U25F=EKN&tN+B8}1{^jH z-_N1JlKhC}*d*@EZYCmsJgS*)Uq?Fq4EApXJM8wzS{S($H5w|~ytP=R*1pYHf9Tw} zv;ioc3<9FRvYN%z2^u_4T7b)Y??+z*_5SLFkvXD#B8LXu@vVMkJ5} z^3y?D}CP|cyn7CB%fJ?c9Se;+Gd!LE`oWl_*=3*>)U3n|>(Pz0a{)=VTchR~` z+;U#7uQT#eF|61rW3d~;>jg}unZTv3$owngkNS+7zuXDU?$UBu<2^kSQ(M4q}2F(l-r+kTx+-Jup4;>ouqN!JHj=qR8Kz2f!HbYRehZb8ge~7+k zGeKh2iia8t1c*$H@_p^%qT?9`tH)sM}&>WS( zz2~K=71h^?L{ph*BWE2@dL9CQaVmr}auyaIY)i%fQBY8CTGYx1PK{*%eew%n)71u* zG(VdbZ-r?lR9xXLur=5dEtTkFmP+Z*ak;;%SI2o>I=`pXeUx=Uty)<%+40IJzm`u; zP)WUZO58^irg!>*zT#o@Hcg3eu=V!2dpiC3HjbKD%Cng>gYn>lEXmB~; z1~eg^;=xsx6dBqrml$W}eT?S$MjCOdE<*d>$6%FUS=7{vz(#w<`|b?;ihF6=jz9#j z;9?0e{Fd&9FCo%nmz|dvK29O_{&kt`6YeOu{V~a@bmQdDYtXP;PUskow(8>mOX5HS zPnn&i8z-_nB%^LQkJMLU)fS84R?9uqkO?f_vpQa>v-qt%p`nMQ7?6f*Gv<>fh^RC* zvLM8WEy4ZL+S796!O+)?6lCp+Syg7gk3!tbTEyT35T(XkEMPo;eBl+GW<#-QC|hT6 zo0~J$&gA<2WlS@vC`n^dP7iOg%cs1jOe@0}2ULXY75wBf_kWy@m&xr!+qtSrnkCI+ zG|!J~*cGp#2UD-M#NJ}t_CGXLeVPAYV{+zbEy=$<&E_!m8BUd;y!obH9hbo-Q18ojoTHaR!>{n`>6uhP92V+R^rj z1}moBwa+FlDToaFomcirP_OsNg(2&JSzdVJ^@){&bBOqbB-9!72@$9P#WZ12xAP=A zbz4qW9{jlI*RPRHtcH%*mQC5{SRa}3S{igD;DS1&qnLXkUJ&}LsWp(~a9OvIot2f& zY9APo&L?6sWO^IN9vZpq6)MSMhL)1B+WH zwaXDW`U^Mlo#7UX*KtNic7@#*FT+DCtB&7y-((`iTj_D9o=hfd!Te;Vu&mOx7= zt?u*dUya;@V-;>zPU*o~ahFxLF>MxCZ&=*8CxL0$2^{=H63cKiKV* z*e0cE`DUB>R8?0d8h&ijCNCAP&VT>03epxgnoVK0RjI)xvbzeLk2t+CJ%d#{7R@yk ze@$Z>4XZSd?%mHNs6VW(x6)5a&!T*I(=vsUa z^pBF4_}fF@)@E=B)eE_E-4hX3o}<%HD-0}il}$XwW296Zu33G44VL}4)7$3MBY8^L zo=eOe|Gl|l10hsD=y68O+eeip(c+{AI{c%_Vti|J#snym{CC4_K*D_gG<>uf93&)rdY+?zHTACVKk*rIl8H5=KG3)=F$xqTVf7TBw$ WU6L~W`s&Va0}6T8yxjCs%zpsTC&WPj literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/new-tab.png b/runelite-client/src/main/resources/net/runelite/client/plugins/banktags/tabs/new-tab.png new file mode 100644 index 0000000000000000000000000000000000000000..d2a1a0c8cfbc7ada6b6917a3acc375363934c5ae GIT binary patch literal 943 zcmV;g15o^lP)2`m>(-06_C)^h4X17w5RTzM04YfKm#lam>I4fQY!sBmuI@PqcI` zkctzQHo68-1;CEnX&j+Qi~+DCW&XBf5P`Db!74-qy3=!Rref9GL-^1AIFiz%N{;v&dA#w1tT$N_jsA_HWmXXlm{C=%ypNvG*K zz(lVRF0BQM02HA}h&JOQQ zJnNirAGz!t42~HfJ9F#DoA@%4o}-w#SfJzh0)WHuA8zmd_NZciIJ!bylQ+99c4wPe ztc;u>KdfhW-2=p;Q!yW`k}K4n?PiVbW}S_zptStCJ79k}Vz=Gk_4dr^kc3i%PH)I2 z+~ty9A@|rKNgZ@z7nYYdCr?u69@=+io>NKB&@0vONCnG<71diwS1GAUSnn2Zne0~5 zNv?gc2jsBa!w7K2kX%GALU?3Z0vqR?sX3F^2xvegIAJd z4&!kN074(M$?5#H0_pE(7r8YVrPd z!^h|SAa@xe{&Swyu`{ofZQB46Bq5H^ZG{Ugj4F0GXtVfVd4%EB6w}#|qO87lC#ZoH?W7_s`r7pG4O-#bo8g1+y zIZJSnTYt`5T@c;zN;GPbXFR9J=0STSxKF$|p1<|iowT<_ZNcJ1Ck5FkLB{A0xwD`_c; zl=dzLd;^^A^HSu998uB|0B%3N3!V%gKmU-G*z3#ftNAp%y?+1zz5xfZU%&r;DIty5 zmmA*RKMrztg9PaOKcDK@Wv!Hy5-5T!g2d5bS~(B^Sp-r7PD3W%L5!$;5{c}L3CUGb z_U|l$J!gnVAj>6UJp(p^7F}7bW3O@zVhkQ3W(aCo4CK7{ZiY}YxeK;!t_%X8Q#O(M zqqpriPdzK=A?j=shtkxs-UOvAgwxF(nrluDPZMs^2(l2YBx~lq(JXc>lXz&gHiDC9 zmLOA4&>tvWM!ni}n$rgEYyrS2Q`4rL5~vyRe6eJ#d}(AYy3CPm9~Wd;;tW8tbkCbV zYg}wtn>iK9kR19M`}~|>HD-xh`88iAuyhYq@EN%!)QqD^NYPTwU$~&0R$>wMIl|GM zrxi#SLz9`dplaOQ+|f#0$0{SIROSS#jkOY}iIl~3nVj1=wNn**1y6Ual~{)>a-Mx7 zYcvH3v={g}8Be;xl-#fnp5I$g(=tLobt29_y2|=vhfRYkrM)!RqMGjYeJ1@wd-S(AvMng_NG{8e3B3~gOTJ(3loIluo4u2iOIYp@#PQMvh%Yx9 z8wmlBw)B4of{5HkVlSm^m)*K~E3Kqrr?HD*ThQ_@(AUT%0$ENS1lP7O@Z#M`_-=1W zF3UkK5{YxxkHma8oF?fmh|do4D4zO-HG$`S)&CD-D0HyCRMGeV0000(pA=BnPEU?GJHH6v#!U5(Wk^@O=$yg>Z&aC$V?HF|q~^ZzK6%%z184P5 z$49$nU}l*2VWSfh=aAa{5g1uT8{T|$gang8pr&#Y3ExT2y8qS>)~$j!ka?T{=Wue( zf;R(8vGp;8n_QK8wD!t8%B3a6FrYF}<%5r!>gdm7eOfKhD%VA+km(q>VLmB5!=d1( zvuXzS#e%#0f}QP6rduxqIMl0rHyJ?y<1(75t*z#8Q|=vuC|ifZCGk*{+|COCZs!H} z4`ojZPD*rZSV+)!u2FEE#UTxi`0E>Q)>N)2mfYOUss2^$PPeJ5ikrJRMY#-q^4$P+ za_ekux8}@fn`E(EadrI{^Pa=^du+aZ!QsJMeC7FdeM?!cMxnI`G)ip(E_PVg!g%OA zSS(lkxcbe8a~#d~n|bG)7bB6&SABd1cQ@+9+-9GqyZNJ4Xa>8EvH`% yIWq&R+PxN>Ci^CXqjGrY13c@GO~Ld1O#ct4n=@h+(`Taq0000