From 7340f19720c78b254cb2283f3a6bef5b45bea4e5 Mon Sep 17 00:00:00 2001 From: Seth Date: Thu, 1 Mar 2018 21:01:39 -0600 Subject: [PATCH] ge: Add search feature Add a search function to the Grand exhange panel that allows you to look up current prices of items. --- .../grandexchange/GrandExchangeConfig.java | 47 ++++ .../grandexchange/GrandExchangeItemPanel.java | 142 +++++++++++ .../grandexchange/GrandExchangeItems.java | 47 ++++ .../grandexchange/GrandExchangePanel.java | 42 +++- .../grandexchange/GrandExchangePlugin.java | 99 ++++++++ .../GrandExchangeSearchPanel.java | 230 ++++++++++++++++++ .../client/plugins/grandexchange/search.png | Bin 0 -> 388 bytes 7 files changed, 601 insertions(+), 6 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/search.png diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java new file mode 100644 index 0000000000..94d4e981c6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Seth + * 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.grandexchange; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "grandexchange", + name = "Grand Exchange", + description = "Configuration for the Grand Exchange" +) +public interface GrandExchangeConfig extends Config +{ + @ConfigItem( + keyName = "quickLookup", + name = "Hotkey lookup (Alt + Left click)", + description = "Configures whether to enable the hotkey lookup for ge searches" + ) + default boolean quickLookup() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java new file mode 100644 index 0000000000..3d1b13c2fb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2018, Seth + * 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.grandexchange; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.NumberFormat; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GrandExchangeItemPanel extends JPanel +{ + GrandExchangeItemPanel(BufferedImage icon, String name, int itemID, int gePrice, Double haPrice) + { + BorderLayout layout = new BorderLayout(); + layout.setHgap(5); + setLayout(layout); + setToolTipText(name); + + Color background = getBackground(); + + addMouseListener(new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent e) + { + setBackground(getBackground().darker().darker()); + } + + @Override + public void mouseExited(MouseEvent e) + { + setBackground(background); + } + + @Override + public void mouseReleased(MouseEvent e) + { + geLink(name, itemID); + } + }); + + setBorder(new CompoundBorder + ( + new LineBorder(getBackground().brighter(), 1), + new EmptyBorder(5, 5, 5, 5) + )); + + // Icon + JLabel itemIcon = new JLabel(); + itemIcon.setIcon(new ImageIcon(icon)); + add(itemIcon, BorderLayout.LINE_START); + + // Item details panel + JPanel rightPanel = new JPanel(new GridLayout(3, 1)); + rightPanel.setOpaque(false); + + // Item name + JLabel itemName = new JLabel(); + itemName.setText(name); + rightPanel.add(itemName); + + // Ge price + JLabel gePriceLabel = new JLabel(); + gePriceLabel.setText(NumberFormat.getInstance().format(gePrice) + " gp"); + gePriceLabel.setForeground(Color.GREEN); + rightPanel.add(gePriceLabel); + + // Alch price + JLabel haPriceLabel = new JLabel(); + haPriceLabel.setText(NumberFormat.getInstance().format(haPrice.intValue()) + " alch"); + haPriceLabel.setForeground(Color.orange); + rightPanel.add(haPriceLabel); + + add(rightPanel, BorderLayout.CENTER); + } + + public void geLink(String name, int itemID) + { + String url = "http://services.runescape.com/m=itemdb_oldschool/" + name.replaceAll(" ", "_") + "/viewitem?obj=" + itemID; + + if (!Desktop.isDesktopSupported()) + { + log.info("Desktop is not supported. Visit {}", url); + return; + } + + Desktop desktop = Desktop.getDesktop(); + if (!desktop.isSupported(Desktop.Action.BROWSE)) + { + log.info("Desktop browser is not supported. Visit {}", url); + return; + } + + try + { + desktop.browse(new URI(url)); + + log.debug("Opened browser to {}", url); + } + catch (IOException | URISyntaxException ex) + { + log.warn("Unable to open grand exchange page", ex); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java new file mode 100644 index 0000000000..28300b295a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Seth + * 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.grandexchange; + +import java.awt.image.BufferedImage; +import lombok.Data; + +@Data +public class GrandExchangeItems +{ + private final BufferedImage icon; + private final String name; + private final int itemId; + private final int gePrice; + private final double haPrice; + + GrandExchangeItems(BufferedImage icon, String name, int itemId, int gePrice, double haPrice) + { + this.icon = icon; + this.name = name; + this.itemId = itemId; + this.gePrice = gePrice; + this.haPrice = haPrice; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java index c7db78ab89..1c956029cf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java @@ -25,9 +25,15 @@ package net.runelite.client.plugins.grandexchange; +import java.awt.BorderLayout; +import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.swing.BoxLayout; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; import net.runelite.api.GrandExchangeOffer; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.PluginPanel; @@ -37,20 +43,34 @@ class GrandExchangePanel extends PluginPanel { private static final int MAX_OFFERS = 8; + @Getter + private GrandExchangeSearchPanel searchPanel; + private GrandExchangeOfferSlot[] offerSlotPanels = new GrandExchangeOfferSlot[MAX_OFFERS]; - @Inject - GrandExchangePanel(ItemManager itemManager) - { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + private JPanel offerPanel = new JPanel(); + private JTabbedPane tabbedPane = new JTabbedPane(); + + @Inject + GrandExchangePanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) + { + setLayout(new BorderLayout()); + add(tabbedPane, BorderLayout.NORTH); + + // Offer Panel + offerPanel.setLayout(new BoxLayout(offerPanel, BoxLayout.Y_AXIS)); for (int i = 0; i < offerSlotPanels.length; ++i) { offerSlotPanels[i] = new GrandExchangeOfferSlot(itemManager); - add(offerSlotPanels[i]); + offerPanel.add(offerSlotPanels[i]); } - setVisible(true); + // Search Panel + searchPanel = new GrandExchangeSearchPanel(client, itemManager, executor); + + tabbedPane.addTab("Offers", offerPanel); + tabbedPane.addTab("Search", searchPanel); } void updateOffer(GrandExchangeOffer newOffer, int slot) @@ -58,4 +78,14 @@ class GrandExchangePanel extends PluginPanel offerSlotPanels[slot].updateOffer(newOffer); } + void showSearch() + { + if (searchPanel.isShowing()) + { + return; + } + + tabbedPane.setSelectedComponent(searchPanel); + revalidate(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index 9e1cd336b9..f9b447e03a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -28,12 +28,24 @@ package net.runelite.client.plugins.grandexchange; import com.google.common.eventbus.Subscribe; +import com.google.inject.Provides; +import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import javax.inject.Inject; import javax.swing.SwingUtilities; +import net.runelite.api.Client; +import net.runelite.api.ItemComposition; +import net.runelite.api.Point; +import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.GrandExchangeOfferChanged; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.input.MouseListener; +import net.runelite.client.input.MouseManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.ClientUI; @@ -48,9 +60,26 @@ public class GrandExchangePlugin extends Plugin private GrandExchangePanel panel; + private MouseListener itemClick; + + @Inject + private MouseManager mouseManager; + + @Inject + private Client client; + @Inject private ClientUI ui; + @Inject + private GrandExchangeConfig config; + + @Provides + GrandExchangeConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(GrandExchangeConfig.class); + } + @Override protected void startUp() throws IOException { @@ -58,12 +87,82 @@ public class GrandExchangePlugin extends Plugin BufferedImage icon = ImageIO.read(getClass().getResourceAsStream("ge_icon.png")); button = new NavigationButton("GE Offers", icon, () -> panel); ui.getPluginToolbar().addNavigation(button); + + itemClick = new MouseListener() + { + @Override + public MouseEvent mouseClicked(MouseEvent e) + { + // Check if left click + alt + if (e.getButton() == MouseEvent.BUTTON1 && e.isAltDown()) + { + Point mousePosition = client.getMouseCanvasPosition(); + Widget inventoryWidget = client.getWidget(WidgetInfo.INVENTORY); + if (inventoryWidget != null && !inventoryWidget.isHidden()) + { + for (WidgetItem item : inventoryWidget.getWidgetItems()) + { + if (item.getCanvasBounds().contains(mousePosition.getX(), mousePosition.getY())) + { + ItemComposition itemComp = client.getItemDefinition(item.getId()); + if (itemComp != null) + { + e.consume(); + + SwingUtilities.invokeLater(() -> + { + panel.showSearch(); + + if (!button.isSelected()) + { + button.doClick(); + } + + panel.getSearchPanel().priceLookup(itemComp.getName()); + }); + } + + break; + } + } + } + } + + return super.mouseClicked(e); + } + }; + + if (config.quickLookup()) + { + mouseManager.registerMouseListener(itemClick); + } } @Override protected void shutDown() { ui.getPluginToolbar().removeNavigation(button); + + mouseManager.unregisterMouseListener(itemClick); + } + + @Subscribe + public void onConfigChange(ConfigChanged event) + { + if (event.getGroup().equals("grandexchange")) + { + if (event.getKey().equals("quickLookup")) + { + if (config.quickLookup()) + { + mouseManager.registerMouseListener(itemClick); + } + else + { + mouseManager.unregisterMouseListener(itemClick); + } + } + } } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java new file mode 100644 index 0000000000..3a6ac59448 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2018, Seth + * 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.grandexchange; + +import com.google.common.base.Strings; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridLayout; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import javax.imageio.ImageIO; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.ItemComposition; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.hiscore.IconTextField; +import net.runelite.http.api.item.Item; +import net.runelite.http.api.item.ItemClient; +import net.runelite.http.api.item.ItemPrice; +import net.runelite.http.api.item.SearchResult; + +@Slf4j +class GrandExchangeSearchPanel extends JPanel +{ + private static final List ITEMS_LIST = new ArrayList<>(); + + private final Client client; + private final ItemManager itemManager; + private final ScheduledExecutorService executor; + + private ItemClient itemClient; + + private Icon search; + + private IconTextField searchBox = new IconTextField(); + private JPanel container = new JPanel(); + private JPanel searchItemsPanel = new JPanel(); + private JLabel searchingLabel = new JLabel("Searching..."); + + GrandExchangeSearchPanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) + { + this.client = client; + this.itemManager = itemManager; + this.executor = executor; + init(); + } + + void init() + { + setLayout(new BorderLayout()); + container.setLayout(new BorderLayout()); + + // Search Box + try + { + search = new ImageIcon(ImageIO.read(GrandExchangePlugin.class.getResourceAsStream("search.png"))); + } + catch (IOException e) + { + log.warn("Failed to read icon", e); + } + + searchBox.setIcon(search); + searchBox.addActionListener(e -> executor.execute(() -> priceLookup(false))); + + container.add(searchBox, BorderLayout.NORTH); + + // Searching label + searchingLabel.setHorizontalAlignment(JLabel.CENTER); + searchingLabel.setForeground(Color.YELLOW); + + // Items Panel + searchItemsPanel.setLayout(new GridLayout(0, 1, 0, 3)); + searchItemsPanel.setBorder(new EmptyBorder(3, 0, 0, 0)); + + container.add(searchItemsPanel, BorderLayout.SOUTH); + add(container, BorderLayout.NORTH); + } + + void priceLookup(String item) + { + searchBox.setText(item); + executor.execute(() -> priceLookup(true)); + } + + private void priceLookup(boolean exactMatch) + { + String lookup = searchBox.getText(); + + if (Strings.isNullOrEmpty(lookup)) + { + return; + } + + // Input is not empty, add searching label + searchItemsPanel.removeAll(); + showSearchString(true); + + SearchResult result; + + try + { + result = itemManager.searchForItem(lookup); + } + catch (ExecutionException ex) + { + log.warn("Unable to search for item {}", lookup, ex); + showSearchString(false); + return; + } + + + if (result != null && !result.getItems().isEmpty()) + { + itemClient = new ItemClient(); + + for (Item item : result.getItems()) + { + int itemId = item.getId(); + + ItemComposition itemComp = client.getItemDefinition(itemId); + if (itemComp == null) + { + continue; + } + + ItemPrice itemPrice; + try + { + itemPrice = itemManager.getItemPrice(itemId); + } + catch (IOException ex) + { + log.warn("Unable to fetch item price for {}", itemId, ex); + showSearchString(false); + return; + } + + BufferedImage itemImage; + try + { + itemImage = itemClient.getIcon(itemId); + } + catch (IOException ex) + { + log.warn("Unable to fetch item icon for {}", itemId, ex); + showSearchString(false); + return; + } + + if (itemImage == null) + { + log.warn("Unable to fetch item icon for {}", itemId); + showSearchString(false); + return; + } + + ITEMS_LIST.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice.getPrice(), itemComp.getPrice() * 0.6)); + + // If using hotkey to lookup item, stop after finding match. + if (exactMatch && item.getName().equalsIgnoreCase(lookup)) + { + break; + } + } + } + + SwingUtilities.invokeLater(() -> + { + for (GrandExchangeItems item : ITEMS_LIST) + { + GrandExchangeItemPanel panel = new GrandExchangeItemPanel(item.getIcon(), item.getName(), item.getItemId(), item.getGePrice(), item.getHaPrice()); + + searchItemsPanel.add(panel); + } + + ITEMS_LIST.clear(); + + // Remove searching label after search is complete + showSearchString(false); + }); + } + + private void showSearchString(boolean shown) + { + if (shown) + { + add(searchingLabel, BorderLayout.CENTER); + } + else + { + remove(searchingLabel); + } + + revalidate(); + repaint(); + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/search.png b/runelite-client/src/main/resources/net/runelite/client/plugins/grandexchange/search.png new file mode 100644 index 0000000000000000000000000000000000000000..23bf66b759f5dbd8eea47cd1316c5fdb0e2c9202 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z$hLzpWB=2SsX#%=64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq!<_&89iMb zLo9lyUfS!$94Ns0;Cu+i8 z{}%+OUeN7azj(Hh*$1IN!n1ZBTrg!$xB3m8)|{tLq6L1NCS5XZFMPMc$7F-h*WWzN zN=*xawL83^_38@w;!X0L%9#nF7M;e)Yo4Y=-zrx0-Mwd(Sf6vm1HE1IocwQn zPPycK=h5LWi+8Oue(6ztj`gv}ywgiG-~JR>exuvk+{t9Rr|c@9bK-^ywktLZ)F-D0 gHhZdz-)M`!&lveI>_gskV2Ckzy85}Sb4q9e0JU(RJpcdz literal 0 HcmV?d00001