Merge pull request #638 from Abextm/widget-listeners

Move widget inspector into it's own window
This commit is contained in:
Adam
2018-03-08 22:36:42 -05:00
committed by GitHub
12 changed files with 617 additions and 651 deletions

View File

@@ -78,8 +78,19 @@ public interface Widget
void setSpriteId(int spriteId);
/**
* @return True if this widget or any of it's parents are hidden
*/
boolean isHidden();
/**
* @return True if this widget, regardless of it's parent's state
*/
boolean isSelfHidden();
/**
* Sets if this element is hidden as returned by isSelfHidden()
*/
void setHidden(boolean hidden);
Point getCanvasLocation();

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018 Abex
* 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.devtools;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup(
keyName = "devtools",
name = "Dev Tools",
description = "Configuration for developer tools"
)
public interface DevToolsConfig
{
@ConfigItem(
keyName = "inspectorAlwaysOnTop",
name = "",
description = "",
hidden = true
)
default boolean inspectorAlwaysOnTop()
{
return false;
}
@ConfigItem(
keyName = "inspectorAlwaysOnTop",
name = "",
description = ""
)
void inspectorAlwaysOnTop(boolean value);
}

View File

@@ -25,88 +25,46 @@
*/
package net.runelite.client.plugins.devtools;
import static net.runelite.api.widgets.WidgetInfo.TO_CHILD;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.ui.PluginPanel;
@Slf4j
public class DevToolsPanel extends PluginPanel
{
private JButton renderPlayersBtn = new JButton();
private JButton renderNpcsBtn = new JButton();
private JButton renderGroundItemsBtn = new JButton();
private JButton renderGroundObjectsBtn = new JButton();
private JButton renderGameObjectsBtn = new JButton();
private JButton renderWallsBtn = new JButton();
private JButton renderDecorBtn = new JButton();
private JButton renderInventoryBtn = new JButton();
private JButton renderProjectilesBtn = new JButton();
private JPanel boundsDebugPanel = new JPanel();
private JButton renderLocationBtn = new JButton();
private JLabel textLbl = new JLabel();
private JLabel textColorLbl = new JLabel();
private JLabel nameLbl = new JLabel();
private JLabel modelLbl = new JLabel();
private JLabel textureLbl = new JLabel();
private JLabel typeLbl = new JLabel();
private JLabel contentTypeLbl = new JLabel();
private JButton editWidgetBtn = new JButton();
private JPanel borderedWrap = null;
private JPanel widgetTreeContainer = null;
private WidgetEditPanel editWidgetPanel = null;
private boolean editActive = false;
private final Client client;
private final DevToolsPlugin plugin;
private final SettingsTracker settingsTracker;
private WidgetInspector widgetInspector;
@Inject
public DevToolsPanel(Client client, DevToolsPlugin plugin)
public DevToolsPanel(Client client, DevToolsPlugin plugin, WidgetInspector widgetInspector)
{
super();
this.client = client;
this.plugin = plugin;
this.widgetInspector = widgetInspector;
settingsTracker = new SettingsTracker(client);
borderedWrap = new JPanel();
borderedWrap.setLayout(new BorderLayout(0, 3));
borderedWrap.add(createOptionsPanel(), BorderLayout.NORTH);
borderedWrap.add(createWidgetTreePanel(), BorderLayout.CENTER);
add(borderedWrap);
add(createOptionsPanel());
}
private JPanel createOptionsPanel()
{
JPanel container = new JPanel();
container.setLayout(new GridLayout(7, 2, 3, 3));
final JPanel container = new JPanel();
container.setLayout(new GridLayout(0, 2, 3, 3));
renderPlayersBtn = new JButton("Players");
final JButton renderPlayersBtn = new JButton("Players");
renderPlayersBtn.addActionListener(e ->
{
highlightButton(renderPlayersBtn);
@@ -114,7 +72,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderPlayersBtn);
renderNpcsBtn = new JButton("NPCs");
final JButton renderNpcsBtn = new JButton("NPCs");
renderNpcsBtn.addActionListener(e ->
{
highlightButton(renderNpcsBtn);
@@ -122,7 +80,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderNpcsBtn);
renderGroundItemsBtn = new JButton("Ground Items");
final JButton renderGroundItemsBtn = new JButton("Ground Items");
renderGroundItemsBtn.addActionListener(e ->
{
highlightButton(renderGroundItemsBtn);
@@ -130,7 +88,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderGroundItemsBtn);
renderGroundObjectsBtn = new JButton("Ground Objects");
final JButton renderGroundObjectsBtn = new JButton("Ground Objects");
renderGroundObjectsBtn.addActionListener(e ->
{
highlightButton(renderGroundObjectsBtn);
@@ -138,7 +96,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderGroundObjectsBtn);
renderGameObjectsBtn = new JButton("Game Objects");
final JButton renderGameObjectsBtn = new JButton("Game Objects");
renderGameObjectsBtn.addActionListener(e ->
{
highlightButton(renderGameObjectsBtn);
@@ -146,7 +104,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderGameObjectsBtn);
renderWallsBtn = new JButton("Walls");
final JButton renderWallsBtn = new JButton("Walls");
renderWallsBtn.addActionListener(e ->
{
highlightButton(renderWallsBtn);
@@ -154,7 +112,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderWallsBtn);
renderDecorBtn = new JButton("Decorations");
final JButton renderDecorBtn = new JButton("Decorations");
renderDecorBtn.addActionListener(e ->
{
highlightButton(renderDecorBtn);
@@ -162,7 +120,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderDecorBtn);
renderInventoryBtn = new JButton("Inventory");
final JButton renderInventoryBtn = new JButton("Inventory");
renderInventoryBtn.addActionListener(e ->
{
highlightButton(renderInventoryBtn);
@@ -170,7 +128,7 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderInventoryBtn);
renderProjectilesBtn = new JButton("Projectiles");
final JButton renderProjectilesBtn = new JButton("Projectiles");
renderProjectilesBtn.addActionListener(e ->
{
highlightButton(renderProjectilesBtn);
@@ -178,18 +136,18 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderProjectilesBtn);
boundsDebugPanel = createBoundsDebugMultiButton();
final JPanel boundsDebugPanel = createBoundsDebugMultiButton();
container.add(boundsDebugPanel);
JButton settingsSnapshotBtn = new JButton("Get Settings");
final JButton settingsSnapshotBtn = new JButton("Get Settings");
settingsSnapshotBtn.addActionListener(settingsTracker::snapshot);
container.add(settingsSnapshotBtn);
JButton settingsClearBtn = new JButton("Clear Settings");
final JButton settingsClearBtn = new JButton("Clear Settings");
settingsClearBtn.addActionListener(settingsTracker::clear);
container.add(settingsClearBtn);
renderLocationBtn = new JButton("Location");
final JButton renderLocationBtn = new JButton("Location");
renderLocationBtn.addActionListener(e ->
{
highlightButton(renderLocationBtn);
@@ -197,6 +155,15 @@ public class DevToolsPanel extends PluginPanel
});
container.add(renderLocationBtn);
final JButton widgetInspectorBtn = new JButton("Inspector");
widgetInspectorBtn.addActionListener(e ->
{
widgetInspector.setVisible(true);
widgetInspector.toFront();
widgetInspector.repaint();
});
container.add(widgetInspectorBtn);
return container;
}
@@ -267,150 +234,6 @@ public class DevToolsPanel extends PluginPanel
return buttonPanel;
}
private void editWidget(Widget w)
{
if (editWidgetPanel == null)
{
editWidgetPanel = new WidgetEditPanel(this, w);
}
else
{
editWidgetPanel.setWidget(w);
}
widgetTreeContainer.setVisible(false);
this.borderedWrap.remove(widgetTreeContainer);
this.borderedWrap.add(editWidgetPanel, BorderLayout.CENTER);
editWidgetPanel.validate();
editWidgetPanel.setVisible(true);
editWidgetPanel.repaint();
editActive = true;
}
public void cancelEdit()
{
if (!editActive)
return;
editWidgetPanel.setVisible(false);
this.borderedWrap.remove(editWidgetPanel);
this.borderedWrap.add(widgetTreeContainer, BorderLayout.CENTER);
widgetTreeContainer.revalidate();
widgetTreeContainer.setVisible(true);
widgetTreeContainer.repaint();
}
private JPanel createWidgetTreePanel()
{
widgetTreeContainer = new JPanel();
widgetTreeContainer.setLayout(new BorderLayout(0, 3));
JTree tree = new JTree(new DefaultMutableTreeNode());
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.getSelectionModel().addTreeSelectionListener(e ->
{
Object selected = tree.getLastSelectedPathComponent();
if (selected instanceof WidgetTreeNode)
{
WidgetTreeNode node = (WidgetTreeNode) selected;
Widget widget = node.getWidget();
plugin.currentWidget = widget;
plugin.itemIndex = widget.getItemId();
setWidgetInfo(widget);
log.debug("Set widget to {} and item index to {}", widget, widget.getItemId());
}
else if (selected instanceof WidgetItemNode)
{
WidgetItemNode node = (WidgetItemNode) selected;
plugin.itemIndex = node.getWidgetItem().getIndex();
log.debug("Set item index to {}", plugin.itemIndex);
}
});
JScrollPane scrollPane = new JScrollPane(tree);
widgetTreeContainer.add(scrollPane, BorderLayout.CENTER);
JButton refreshWidgetsBtn = new JButton("Refresh Widgets");
refreshWidgetsBtn.addActionListener(e ->
{
new SwingWorker<DefaultMutableTreeNode, Void>()
{
@Override
protected DefaultMutableTreeNode doInBackground() throws Exception
{
return refreshWidgets();
}
@Override
protected void done()
{
try
{
tree.setModel(new DefaultTreeModel(get()));
}
catch (InterruptedException | ExecutionException ex)
{
throw new RuntimeException(ex);
}
}
}.execute();
});
JPanel btnContainer = new JPanel();
btnContainer.setLayout(new BorderLayout());
btnContainer.add(refreshWidgetsBtn);
widgetTreeContainer.add(btnContainer, BorderLayout.NORTH);
JPanel infoContainer = new JPanel();
infoContainer.setLayout(new GridLayout(0, 1));
textLbl = new JLabel("Text: ");
textColorLbl = new JLabel("Text Color: ");
nameLbl = new JLabel("Name: ");
modelLbl = new JLabel("Model ID: ");
textureLbl = new JLabel("Texture ID: ");
typeLbl = new JLabel("Type: ");
contentTypeLbl = new JLabel("Content Type: ");
editWidgetBtn = new JButton("Edit");
editWidgetBtn.addActionListener(e ->
{
editWidget(plugin.currentWidget);
});
infoContainer.add(textLbl);
infoContainer.add(textColorLbl);
infoContainer.add(nameLbl);
infoContainer.add(modelLbl);
infoContainer.add(textureLbl);
infoContainer.add(typeLbl);
infoContainer.add(contentTypeLbl);
infoContainer.add(editWidgetBtn);
JScrollPane infoScrollPane = new JScrollPane(infoContainer);
infoScrollPane.setBorder(new EmptyBorder(6, 6, 6, 6));
widgetTreeContainer.add(infoScrollPane, BorderLayout.SOUTH);
return widgetTreeContainer;
}
private void setWidgetInfo(Widget widget)
{
if (widget == null)
{
return;
}
textLbl.setText("Text: " + widget.getText().trim());
textColorLbl.setText("Text Color: " + widget.getTextColor());
nameLbl.setText("Name: " + widget.getName().trim());
modelLbl.setText("Model ID: " + widget.getModelId());
textureLbl.setText("Sprite ID: " + widget.getSpriteId());
typeLbl.setText("Type: " + widget.getType()
+ " Parent " + (widget.getParentId() == -1 ? -1 : TO_GROUP(widget.getParentId()) + "." + TO_CHILD(widget.getParentId())));
contentTypeLbl.setText("Content Type: " + widget.getContentType() + " Hidden " + widget.isHidden());
}
private void highlightButton(JButton button)
{
if (button.getBackground().equals(Color.GREEN))
@@ -422,90 +245,4 @@ public class DevToolsPanel extends PluginPanel
button.setBackground(Color.GREEN);
}
}
private DefaultMutableTreeNode refreshWidgets()
{
Widget[] rootWidgets = client.getWidgetRoots();
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
plugin.currentWidget = null;
plugin.itemIndex = -1;
for (Widget widget : rootWidgets)
{
DefaultMutableTreeNode childNode = addWidget("R", widget);
if (childNode != null)
{
root.add(childNode);
}
}
return root;
}
private DefaultMutableTreeNode addWidget(String type, Widget widget)
{
if (widget == null || widget.isHidden())
{
return null;
}
DefaultMutableTreeNode node = new WidgetTreeNode(type, widget);
Widget[] childComponents = widget.getDynamicChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("D", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
childComponents = widget.getStaticChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("S", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
childComponents = widget.getNestedChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("N", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
Collection<WidgetItem> items = widget.getWidgetItems();
if (items != null)
{
for (WidgetItem item : items)
{
if (item == null)
{
continue;
}
node.add(new WidgetItemNode(item));
}
}
return node;
}
}

View File

@@ -24,6 +24,7 @@
*/
package net.runelite.client.plugins.devtools;
import com.google.inject.Provides;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.util.Arrays;
@@ -31,6 +32,7 @@ import java.util.Collection;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import net.runelite.api.widgets.Widget;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.ClientUI;
@@ -70,6 +72,12 @@ public class DevToolsPlugin extends Plugin
private Font font;
private NavigationButton navButton;
@Provides
DevToolsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(DevToolsConfig.class);
}
@Override
protected void startUp() throws Exception
{

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2018, Dreyri <https://github.com/Dreyri>
* 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.devtools;
import lombok.extern.slf4j.Slf4j;
import javax.swing.JTextField;
@Slf4j
public class NumberField extends JTextField
{
private int number;
public NumberField()
{
super();
}
public NumberField(int number)
{
super(String.valueOf(number));
this.number = number;
}
public NumberField(String text)
{
super(text);
}
public int getNumber()
{
try
{
this.number = Integer.valueOf(super.getText());
}
catch (NumberFormatException e)
{
log.warn("Not a number!", e);
super.setText(String.valueOf(this.number));
}
return this.number;
}
}

View File

@@ -1,290 +0,0 @@
/*
* Copyright (c) 2018, Dreyri <https://github.com/Dreyri>
* 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.devtools;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.widgets.Widget;
@Slf4j
public class WidgetEditPanel extends JPanel
{
private Widget widget;
private JLabel nameLbl = new JLabel();
private JLabel textLbl = new JLabel();
private JLabel typeLbl = new JLabel();
private JLabel contentTypeLbl = new JLabel();
private JLabel textColorLbl = new JLabel();
private JLabel hiddenLbl = new JLabel();
private JLabel spriteLbl = new JLabel();
private JLabel relativeXLbl = new JLabel();
private JLabel relativeYLbl = new JLabel();
private JLabel widthLbl = new JLabel();
private JLabel heightLbl = new JLabel();
private JTextField nameTF = new JTextField();
private JTextField textTF = new JTextField();
private NumberField typeTF = new NumberField();
private NumberField contentTypeTF = new NumberField();
private NumberField textColorTF = new NumberField();
private JCheckBox hiddenCB = new JCheckBox();
private NumberField spriteTF = new NumberField();
private NumberField relativeXTF = new NumberField();
private NumberField relativeYTF = new NumberField();
private NumberField widthTF = new NumberField();
private NumberField heightTF = new NumberField();
private JButton cancelBtn = new JButton();
private JButton applyBtn = new JButton();
private final DevToolsPanel owner;
public WidgetEditPanel(DevToolsPanel panel, Widget w)
{
this.owner = panel;
this.widget = w;
init();
}
private void init()
{
createInputElements();
this.setLayout(new GridBagLayout());
errorCheck();
createInterfaceElements();
setupInterface();
}
private void errorCheck()
{
if (widget == null)
{
JLabel errorLabel = new JLabel("Widget could not be loaded!");
errorLabel.setForeground(Color.RED);
this.add(errorLabel);
}
}
public void setWidget(Widget w)
{
this.removeAll();
this.widget = w;
init();
}
private void createInputElements()
{
if (widget == null)
return;
nameTF = new JTextField(widget.getName());
textTF = new JTextField(widget.getText());
typeTF = new NumberField(widget.getType());
contentTypeTF = new NumberField(widget.getContentType());
textColorTF = new NumberField(widget.getTextColor());
hiddenCB = new JCheckBox();
spriteTF = new NumberField(widget.getSpriteId());
relativeXTF = new NumberField(widget.getRelativeX());
relativeYTF = new NumberField(widget.getRelativeY());
widthTF = new NumberField(widget.getWidth());
heightTF = new NumberField(widget.getHeight());
hiddenCB.setSelected(widget.isHidden());
}
private void createInterfaceElements()
{
nameLbl = new JLabel("Name");
textLbl = new JLabel("Text");
typeLbl = new JLabel("Type");
contentTypeLbl = new JLabel("Content type");
textColorLbl = new JLabel(String.valueOf("Text color"));
hiddenLbl = new JLabel("Hidden");
spriteLbl = new JLabel("Sprite");
relativeXLbl = new JLabel("Relative X");
relativeYLbl = new JLabel("Relative Y");
widthLbl = new JLabel("Width");
heightLbl = new JLabel("Height");
cancelBtn = new JButton("Cancel");
applyBtn = new JButton("Apply");
this.cancelBtn.addActionListener(e ->
{
owner.cancelEdit();
});
this.applyBtn.addActionListener(e ->
{
applyChanges();
});
}
/**
* only change when actual change happened to prevent sending too many change events
*/
private void applyChanges()
{
boolean hidden = hiddenCB.isSelected();
String name = nameTF.getText();
String text = textTF.getText();
int type = typeTF.getNumber();
int contentType = contentTypeTF.getNumber();
int textColor = textColorTF.getNumber();
int sprite = spriteTF.getNumber();
int relativeX = relativeXTF.getNumber();
int relativeY = relativeYTF.getNumber();
int width = widthTF.getNumber();
int height = heightTF.getNumber();
if (hidden != widget.isHidden())
widget.setHidden(hidden);
if (!name.equals(widget.getName()))
widget.setName(name);
if (!text.equals(widget.getText()))
widget.setText(text);
if (type != widget.getType())
widget.setType(type);
if (contentType != widget.getContentType())
widget.setContentType(contentType);
if (textColor != widget.getTextColor())
widget.setTextColor(textColor);
if (sprite != widget.getSpriteId())
widget.setSpriteId(sprite);
if (relativeX != widget.getRelativeX())
widget.setRelativeX(relativeX);
if (relativeY != widget.getRelativeY())
widget.setRelativeY(relativeY);
if (width != widget.getWidth())
widget.setWidth(width);
if (height != widget.getHeight())
widget.setHeight(height);
}
private void setupInterface()
{
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 0.5;
constraints.gridwidth = 1;
constraints.gridx = 0;
constraints.gridy = 2;
this.add(nameLbl, constraints);
constraints.gridx = 1;
this.add(nameTF, constraints);
constraints.gridx = 0;
constraints.gridy = 3;
this.add(textLbl, constraints);
constraints.gridx = 1;
this.add(textTF, constraints);
constraints.gridx = 0;
constraints.gridy = 4;
this.add(typeLbl, constraints);
constraints.gridx = 1;
this.add(typeTF, constraints);
constraints.gridx = 0;
constraints.gridy = 5;
this.add(contentTypeLbl, constraints);
constraints.gridx = 1;
this.add(contentTypeTF, constraints);
constraints.gridx = 0;
constraints.gridy = 6;
this.add(textColorLbl, constraints);
constraints.gridx = 1;
this.add(textColorTF, constraints);
constraints.gridx = 0;
constraints.gridy = 7;
this.add(hiddenLbl, constraints);
constraints.gridx = 1;
this.add(hiddenCB, constraints);
constraints.gridx = 0;
constraints.gridy = 8;
this.add(spriteLbl, constraints);
constraints.gridx = 1;
this.add(spriteTF, constraints);
constraints.gridx = 0;
constraints.gridy = 9;
this.add(relativeXLbl, constraints);
constraints.gridx = 1;
this.add(relativeXTF, constraints);
constraints.gridx = 0;
constraints.gridy = 10;
this.add(relativeYLbl, constraints);
constraints.gridx = 1;
this.add(relativeYTF, constraints);
constraints.gridx = 0;
constraints.gridy = 11;
this.add(widthLbl, constraints);
constraints.gridx = 1;
this.add(widthTF, constraints);
constraints.gridx = 0;
constraints.gridy = 12;
this.add(heightLbl, constraints);
constraints.gridx = 1;
this.add(heightTF, constraints);
constraints.gridx = 0;
constraints.gridy = 13;
this.add(cancelBtn, constraints);
constraints.gridx = 1;
this.add(applyBtn, constraints);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2018 Abex
* 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.devtools;
import java.util.function.BiConsumer;
import java.util.function.Function;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.widgets.Widget;
import org.slf4j.helpers.MessageFormatter;
@Slf4j
public class WidgetField<T>
{
@Getter
private final String name;
private final Function<Widget, T> getter;
private final BiConsumer<Widget, T> setter;
private final Class<T> type;
WidgetField(String name, Function<Widget, T> getter)
{
this(name, getter, null, null);
}
WidgetField(String name, Function<Widget, T> getter, BiConsumer<Widget, T> setter, Class<T> type)
{
this.name = name;
this.getter = getter;
this.setter = setter;
this.type = type;
}
Object getValue(Widget widget)
{
Object value = getter.apply(widget);
// These types are handled by the JTable automatically
if (value instanceof Boolean || value instanceof Number || value instanceof String)
{
return value;
}
return MessageFormatter.format("{}", value).getMessage();
}
void setValue(Widget widget, Object inValue)
{
Object value = null;
if ("null".equals(inValue))
{
value = null;
}
if (type.isAssignableFrom(inValue.getClass()))
{
value = inValue;
}
else if (type == Boolean.class)
{
value = Boolean.valueOf((String) inValue);
}
else if (type == Integer.class)
{
value = Integer.valueOf((String) inValue);
}
else
{
log.warn("Type {} is not supported for editing", type);
}
setter.accept(widget, (T) value);
}
boolean isSettable()
{
return setter != null;
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2018 Abex
* 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.devtools;
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import net.runelite.api.widgets.Widget;
public class WidgetInfoTableModel extends AbstractTableModel
{
private static final int COL_FIELD = 0;
private static final int COL_VALUE = 1;
private static final List<WidgetField> fields = populateWidgetFields();
private Widget widget = null;
public void setWidget(Widget w)
{
this.widget = w;
fireTableStructureChanged();
}
@Override
public String getColumnName(int col)
{
switch (col)
{
case COL_FIELD:
return "Field";
case COL_VALUE:
return "Value";
default:
return null;
}
}
@Override
public int getColumnCount()
{
return 2;
}
@Override
public int getRowCount()
{
if (widget == null)
{
return 0;
}
return fields.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
WidgetField<?> field = fields.get(rowIndex);
switch (columnIndex)
{
case COL_FIELD:
return field.getName();
case COL_VALUE:
return field.getValue(widget);
default:
return null;
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
if (columnIndex == COL_VALUE)
{
WidgetField<?> field = fields.get(rowIndex);
return field.isSettable();
}
return false;
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex)
{
WidgetField<?> field = fields.get(rowIndex);
field.setValue(widget, value);
}
private static List<WidgetField> populateWidgetFields()
{
List<WidgetField> out = new ArrayList<>();
out.add(new WidgetField<>("Id", Widget::getId));
out.add(new WidgetField<>("Type", Widget::getType, Widget::setType, Integer.class));
out.add(new WidgetField<>("ContentType", Widget::getContentType, Widget::setContentType, Integer.class));
out.add(new WidgetField<>("ParentId", Widget::getParentId));
out.add(new WidgetField<>("SelfHidden", Widget::isSelfHidden, Widget::setHidden, Boolean.class));
out.add(new WidgetField<>("Hidden", Widget::isHidden));
out.add(new WidgetField<>("Text", Widget::getText, Widget::setText, String.class));
out.add(new WidgetField<>("TextColor",
w -> Integer.toString(w.getTextColor(), 16),
(w, str) -> w.setTextColor(Integer.parseInt(str, 16)),
String.class
));
out.add(new WidgetField<>("Name", w -> w.getName().trim(), Widget::setName, String.class));
out.add(new WidgetField<>("ItemId", Widget::getItemId));
out.add(new WidgetField<>("ItemQuantity", Widget::getItemQuantity));
out.add(new WidgetField<>("ModelId", Widget::getModelId));
out.add(new WidgetField<>("SpriteId", Widget::getSpriteId, Widget::setSpriteId, Integer.class));
out.add(new WidgetField<>("Width", Widget::getWidth, Widget::setWidth, Integer.class));
out.add(new WidgetField<>("Height", Widget::getHeight, Widget::setHeight, Integer.class));
out.add(new WidgetField<>("RelativeX", Widget::getRelativeX, Widget::setRelativeX, Integer.class));
out.add(new WidgetField<>("RelativeY", Widget::getRelativeY, Widget::setRelativeY, Integer.class));
out.add(new WidgetField<>("CanvasLocation", Widget::getCanvasLocation));
out.add(new WidgetField<>("Bounds", Widget::getBounds));
out.add(new WidgetField<>("ScrollX", Widget::getScrollX));
out.add(new WidgetField<>("ScrollY", Widget::getScrollY));
out.add(new WidgetField<>("OriginalX", Widget::getOriginalX));
out.add(new WidgetField<>("OriginalY", Widget::getOriginalY));
out.add(new WidgetField<>("PaddingX", Widget::getPaddingX));
out.add(new WidgetField<>("PaddingY", Widget::getPaddingY));
return out;
}
}

View File

@@ -0,0 +1,265 @@
/*
* Copyright (c) 2018 Abex
* Copyright (c) 2017, Kronos <https://github.com/KronosDesign>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.devtools;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingWorker;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.ui.ClientUI;
@Slf4j
class WidgetInspector extends JFrame
{
private final Client client;
private final DevToolsPlugin plugin;
private final DevToolsConfig config;
private final JTree widgetTree;
private final WidgetInfoTableModel infoTableModel;
private final JCheckBox alwaysOnTop;
@Inject
WidgetInspector(DevToolsPlugin plugin, Client client, WidgetInfoTableModel infoTableModel, DevToolsConfig config, EventBus eventBus)
{
this.plugin = plugin;
this.client = client;
this.infoTableModel = infoTableModel;
this.config = config;
eventBus.register(this);
setTitle("RuneLite Widget Inspector");
setIconImage(ClientUI.ICON);
// Reset highlight on close
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
plugin.currentWidget = null;
plugin.itemIndex = -1;
}
});
setLayout(new BorderLayout());
widgetTree = new JTree(new DefaultMutableTreeNode());
widgetTree.setRootVisible(false);
widgetTree.setShowsRootHandles(true);
widgetTree.getSelectionModel().addTreeSelectionListener(e ->
{
Object selected = widgetTree.getLastSelectedPathComponent();
if (selected instanceof WidgetTreeNode)
{
WidgetTreeNode node = (WidgetTreeNode) selected;
Widget widget = node.getWidget();
plugin.currentWidget = widget;
plugin.itemIndex = widget.getItemId();
refreshInfo();
log.debug("Set widget to {} and item index to {}", widget, widget.getItemId());
}
else if (selected instanceof WidgetItemNode)
{
WidgetItemNode node = (WidgetItemNode) selected;
plugin.itemIndex = node.getWidgetItem().getIndex();
log.debug("Set item index to {}", plugin.itemIndex);
}
});
final JScrollPane treeScrollPane = new JScrollPane(widgetTree);
treeScrollPane.setPreferredSize(new Dimension(200, 400));
final JTable widgetInfo = new JTable(infoTableModel);
final JScrollPane infoScrollPane = new JScrollPane(widgetInfo);
infoScrollPane.setPreferredSize(new Dimension(400, 400));
final JPanel bottomPanel = new JPanel();
add(bottomPanel, BorderLayout.SOUTH);
final JButton refreshWidgetsBtn = new JButton("Refresh");
refreshWidgetsBtn.addActionListener(e -> refreshWidgets());
bottomPanel.add(refreshWidgetsBtn);
alwaysOnTop = new JCheckBox("Always on top");
alwaysOnTop.addItemListener(ev -> config.inspectorAlwaysOnTop(alwaysOnTop.isSelected()));
onConfigChanged(null);
bottomPanel.add(alwaysOnTop);
final JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScrollPane, infoScrollPane);
add(split, BorderLayout.CENTER);
pack();
}
@Subscribe
private void onConfigChanged(ConfigChanged ev)
{
boolean onTop = config.inspectorAlwaysOnTop();
setAlwaysOnTop(onTop);
alwaysOnTop.setSelected(onTop);
}
private void refreshWidgets()
{
new SwingWorker<DefaultMutableTreeNode, Void>()
{
@Override
protected DefaultMutableTreeNode doInBackground() throws Exception
{
Widget[] rootWidgets = client.getWidgetRoots();
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
plugin.currentWidget = null;
plugin.itemIndex = -1;
for (Widget widget : rootWidgets)
{
DefaultMutableTreeNode childNode = addWidget("R", widget);
if (childNode != null)
{
root.add(childNode);
}
}
return root;
}
@Override
protected void done()
{
try
{
plugin.currentWidget = null;
plugin.itemIndex = -1;
refreshInfo();
widgetTree.setModel(new DefaultTreeModel(get()));
}
catch (InterruptedException | ExecutionException ex)
{
throw new RuntimeException(ex);
}
}
}.execute();
}
private DefaultMutableTreeNode addWidget(String type, Widget widget)
{
if (widget == null || widget.isHidden())
{
return null;
}
DefaultMutableTreeNode node = new WidgetTreeNode(type, widget);
Widget[] childComponents = widget.getDynamicChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("D", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
childComponents = widget.getStaticChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("S", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
childComponents = widget.getNestedChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("N", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
Collection<WidgetItem> items = widget.getWidgetItems();
if (items != null)
{
for (WidgetItem item : items)
{
if (item == null)
{
continue;
}
node.add(new WidgetItemNode(item));
}
}
return node;
}
private void refreshInfo()
{
infoTableModel.setWidget(plugin.currentWidget);
}
}

View File

@@ -75,7 +75,7 @@ import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities;
public class ClientUI extends JFrame
{
private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH;
private static final BufferedImage ICON;
public static final BufferedImage ICON;
@Getter
private TrayIcon trayIcon;

View File

@@ -143,7 +143,7 @@ public abstract class RSWidgetMixin implements RSWidget
return true;
}
return isRSHidden();
return isSelfHidden();
}
@Inject

View File

@@ -118,7 +118,8 @@ public interface RSWidget extends Widget
void setHeight(int height);
@Import("isHidden")
boolean isRSHidden();
@Override
boolean isSelfHidden();
@Import("isHidden")
void setHidden(boolean hidden);