ge: Add search feature

Add a search function to the Grand exhange panel that allows you to look up current prices of items.
This commit is contained in:
Seth
2018-03-01 21:01:39 -06:00
committed by Adam
parent 701e641fd5
commit 7340f19720
7 changed files with 601 additions and 6 deletions

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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;
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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);
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -0,0 +1,230 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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<GrandExchangeItems> 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();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B