GT-3054: Updated function tag panel to include table showing all
functions using a tag
|
@ -400,7 +400,7 @@ src/main/help/help/topics/FunctionTagPlugin/images/DeleteWarning.png||GHIDRA||||
|
|||
src/main/help/help/topics/FunctionTagPlugin/images/EditNotAllowedWarning.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionTagPlugin/images/EditTag.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionTagPlugin/images/FilterField.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionTagPlugin/images/FunctionTagPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionTagPlugin/images/InputField.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionWindowPlugin/function_window.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png||GHIDRA||||END|
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<TABLE border="0" width="100%">
|
||||
<TR>
|
||||
<TD width="100%" align="center"><IMG alt="" border="0" src=
|
||||
"images/FunctionTagPlugin.png"></TD>
|
||||
"images/FullWindow.png"></TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</CENTER>
|
||||
|
@ -33,24 +33,24 @@
|
|||
<H2>Window Components</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>This window has five distinct sections:</P>
|
||||
|
||||
<UL>
|
||||
<LI><B>Available Tags List</B>: Displays all tags that are available to assign to the
|
||||
current function.</LI>
|
||||
<LI><B>All Tags TAble</B>: Displays all tags that are available to assign to the
|
||||
current function, as well as a count of the number of times each tag has been used
|
||||
in the current program.</LI>
|
||||
|
||||
<LI><B>Assigned Tags List</B>: Displays all tags that have been assigned to the current
|
||||
function.</LI>
|
||||
|
||||
<LI><B>Function Panel</B>: Displays functions that contain any selected tags. If multiple
|
||||
tags are selected, functions containing ANY of the tags will be displayed.</LI>
|
||||
|
||||
<LI>
|
||||
<B>Tag Input Field</B>: Allows users to create new tags. Multiple tags may be created at one time.
|
||||
|
||||
<P style="margin: 20px;"><IMG alt="" border="0" src="images/InputField.png"></P>
|
||||
</LI>
|
||||
|
||||
<LI>
|
||||
<B>Filter Field</B>: Allows users to filter what is shown in the Available and Assigned tag lists.
|
||||
|
||||
<P style="margin: 20px;"><IMG alt="" border="0" src="images/FilterField.png"></P>
|
||||
</LI>
|
||||
</UL>
|
||||
|
@ -58,10 +58,10 @@
|
|||
<UL class="noindent">
|
||||
<LI><B>Action Buttons</B></LI>
|
||||
<UL>
|
||||
<LI><IMG alt="" border="0" src="images/2rightarrow.png">
|
||||
<LI><IMG alt="" border="0" src="images/2rightarrow.png" width="16" height="16">
|
||||
Assigns the selected tag(s) to the current function</LI>
|
||||
|
||||
<LI><IMG alt="" border="0" src="images/2leftarrow.png">
|
||||
<LI><IMG alt="" border="0" src="images/2leftarrow.png" width="16" height="16">
|
||||
Removes the selected tag(s) from the current function</LI>
|
||||
|
||||
<LI><IMG alt="" border="0" src="Icons.DELETE_ICON">
|
||||
|
@ -80,7 +80,7 @@
|
|||
<BLOCKQUOTE>
|
||||
<P>Tags can be created by using the <I>Tag Input Field</I> described above. Users may enter
|
||||
multiple tag names delimited by a comma. All newly-created tags will be displayed in the
|
||||
Available Tags List and are NOT assigned to any function.</P>
|
||||
Available Tags List but are NOT yet assigned to any function.</P>
|
||||
|
||||
<P><IMG alt="" border="0" src="../../shared/tip.png">Each tag may have an associated comment that
|
||||
is visible as a tooltip. This can be assigned after the tag has been created (see <code>edit</code> section below).</P>
|
||||
|
@ -112,6 +112,14 @@
|
|||
<P>If editing is allowed, the following dialog will be shown:</P>
|
||||
|
||||
<P align="center"><IMG alt="" border="0" src="images/EditTag.png"></P>
|
||||
|
||||
<P><IMG alt="" border="0" src="../../shared/tip.png">An <i>italicized</i> tag name
|
||||
indicates that the tag was loaded from an external source and has not yet been
|
||||
added to the program, making it immutable. As soon as the tag is assigned to a function it becomes
|
||||
editable. If you delete the tag using the <IMG alt="" border="0" src=
|
||||
"Icons.DELETE_ICON" width="16" height="16"> icon it will be removed from
|
||||
the program and once again be immutable.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Add_Tag_To_Function"></A>Add to Function</H3>
|
||||
|
@ -119,7 +127,7 @@
|
|||
<BLOCKQUOTE>
|
||||
<P>Tags may be added to a function by selecting a set of tags and pressing the <IMG alt=""
|
||||
border="0" src="images/2rightarrow.png" width="16" height="16"> button. The tags will be
|
||||
removed from the Available Tags List and added to the Assigned Tags List.</P>
|
||||
added to the Assigned Tags list, and shown as disabled in the Available Tags list.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Remove_Tag_From_Function"></A>Remove from Function</H3>
|
||||
|
@ -134,10 +142,12 @@
|
|||
<H2><a name="loading_tags">Loading External Tags</a></H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Tags may be loaded on startup from an external source if desired. These will be shown in
|
||||
<FONT color="blue">blue</FONT> and cannot be edited or deleted, with one caveat: once a tag has
|
||||
been assigned to a function it ceases to have any special protections and can be edited/deleted
|
||||
just as any other.</P>
|
||||
<P>Tags may be loaded on startup from an external source if desired. These tags will be shown with
|
||||
an asterisk (*) after the name and cannot be edited or deleted; with one caveat: once a tag has
|
||||
been assigned to a function it ceases to have any special protections and can be edited
|
||||
like any other. If the tag is ever removed from all functions using the
|
||||
<IMG alt="" border="0" src="Icons.DELETE_ICON"> button, it will again be present in the
|
||||
Available Tags list.</P>
|
||||
|
||||
<P>To make these available there must be a file named <I>functionTags.xml</I> available on
|
||||
the classpath. Edit (or create) this file and add tags as needed. The format is
|
||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2 KiB |
|
@ -0,0 +1,127 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionTag;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.table.GhidraThreadedTablePanel;
|
||||
|
||||
/**
|
||||
* Displays all functions that are associated with the selected tag in the
|
||||
* {@link SourceTagsPanel}
|
||||
*/
|
||||
public class AllFunctionsPanel extends JPanel {
|
||||
|
||||
private FunctionTableModel model;
|
||||
private JLabel titleLabel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param program the current program
|
||||
* @param provider the component provider
|
||||
* @param title the title of the panel
|
||||
*/
|
||||
public AllFunctionsPanel(Program program, ComponentProviderAdapter provider, String title) {
|
||||
|
||||
model = new FunctionTableModel(title, provider.getTool(), program, null);
|
||||
GhidraThreadedTablePanel<Function> tablePanel = new GhidraThreadedTablePanel<>(model);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
titleLabel = new JLabel(title);
|
||||
titleLabel.setBorder(BorderFactory.createEmptyBorder(3, 5, 0, 0));
|
||||
|
||||
add(titleLabel, BorderLayout.NORTH);
|
||||
add(tablePanel, BorderLayout.CENTER);
|
||||
|
||||
GoToService goToService = provider.getTool().getService(GoToService.class);
|
||||
if (goToService != null) {
|
||||
tablePanel.getTable().installNavigation(goToService, goToService.getDefaultNavigatable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the table with whatever is in the {@link #model}
|
||||
*/
|
||||
public void refresh() {
|
||||
model.fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the table with functions containing the selected tags given
|
||||
*
|
||||
* @param selectedTags the selected function tags
|
||||
*/
|
||||
public void refresh(List<FunctionTag> selectedTags) {
|
||||
setSelectedTags(selectedTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the current program
|
||||
*
|
||||
* @param program the current program
|
||||
*/
|
||||
public void setProgram(Program program) {
|
||||
model.setProgram(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the panel with the set of tags selected by the user. This
|
||||
* will update the panel title and the contents of the function table.
|
||||
*
|
||||
* @param tags the selected tags
|
||||
*/
|
||||
public void setSelectedTags(List<FunctionTag> tags) {
|
||||
String tagNames = tags.stream()
|
||||
.map(t -> t.getName())
|
||||
.collect(Collectors.joining(" or "))
|
||||
.toString();
|
||||
|
||||
titleLabel.setText("Functions With Tag: " + tagNames);
|
||||
model.setSelectedTags(tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of functions in the panel
|
||||
* <p>
|
||||
* This is only used for testing!
|
||||
*
|
||||
* @return the list of functions
|
||||
*/
|
||||
public List<Function> getFunctions() {
|
||||
return model.getFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the functions table model
|
||||
*
|
||||
* @return the functions table model
|
||||
*/
|
||||
public FunctionTableModel getTableModel() {
|
||||
return model;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.table.DiscoverableTableUtils;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.AddressBasedTableModel;
|
||||
import ghidra.util.table.field.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* The data model that backs the {@link AllFunctionsPanel}. This displays a list
|
||||
* of functions that have function tags matching a provided set. Note that
|
||||
* a function will be displayed as long as it has AT LEAST ONE of the tags
|
||||
* in the set.
|
||||
*/
|
||||
class FunctionTableModel extends AddressBasedTableModel<Function> {
|
||||
|
||||
// The function tags to display functions for
|
||||
private List<FunctionTag> tags;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param title the title of the model
|
||||
* @param serviceProvider the service provider
|
||||
* @param program the current program
|
||||
* @param monitor the task monitor
|
||||
*/
|
||||
public FunctionTableModel(String title, ServiceProvider serviceProvider, Program program,
|
||||
TaskMonitor monitor) {
|
||||
super(title, serviceProvider, program, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress(int row) {
|
||||
Function rowObject = getRowObject(row);
|
||||
return rowObject.getEntryPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<Function> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<Function> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(
|
||||
DiscoverableTableUtils.adaptColumForModel(this, new LabelTableColumn()));
|
||||
descriptor.addVisibleColumn(
|
||||
DiscoverableTableUtils.adaptColumForModel(this, new AddressTableColumn()), 1, true);
|
||||
descriptor.addVisibleColumn(
|
||||
DiscoverableTableUtils.adaptColumForModel(this, new FunctionTagTableColumn()));
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<Function> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
if (program == null) {
|
||||
return;
|
||||
}
|
||||
if (tags == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over all functions in the program, filtering out those that do not
|
||||
// do not contain at least one of the tags in the provided set.
|
||||
FunctionIterator iter = program.getFunctionManager().getFunctions(true);
|
||||
monitor.initialize(program.getFunctionManager().getFunctionCount());
|
||||
while (iter.hasNext()) {
|
||||
monitor.incrementProgress(1);
|
||||
monitor.checkCanceled();
|
||||
Function f = iter.next();
|
||||
boolean hasTag = f.getTags().stream().anyMatch(t -> tags.contains(t));
|
||||
if (hasTag) {
|
||||
accumulator.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tags associated with this model. This causes a reload of
|
||||
* the function table
|
||||
*
|
||||
* @param tags the selected tags
|
||||
*/
|
||||
public void setSelectedTags(List<FunctionTag> tags) {
|
||||
this.tags = tags;
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of functions in the table
|
||||
*
|
||||
* @return the contents of the table
|
||||
*/
|
||||
public List<Function> getFunctions() {
|
||||
return getAllData();
|
||||
}
|
||||
}
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -70,8 +69,9 @@ public class FunctionTagButtonPanel extends JPanel {
|
|||
|
||||
boolean hasSelection = sourcePanel.hasSelection();
|
||||
boolean isImmutable = sourcePanel.isSelectionImmutable();
|
||||
boolean isEnabled = sourcePanel.isSelectionEnabled();
|
||||
|
||||
addBtn.setEnabled(hasSelection && validFunction);
|
||||
addBtn.setEnabled(hasSelection && validFunction && isEnabled);
|
||||
removeBtn.setEnabled(false);
|
||||
|
||||
if (!hasSelection) {
|
||||
|
@ -112,7 +112,9 @@ public class FunctionTagButtonPanel extends JPanel {
|
|||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
addBtn = createButton("addBtn", ADD_IMG, "Add selected tags to the function",
|
||||
e -> sourcePanel.addSelectedTags());
|
||||
e -> {
|
||||
sourcePanel.addSelectedTags();
|
||||
});
|
||||
add(addBtn, gbc);
|
||||
|
||||
gbc.gridy = 1;
|
||||
|
@ -127,6 +129,9 @@ public class FunctionTagButtonPanel extends JPanel {
|
|||
targetPanel.deleteSelectedTags();
|
||||
});
|
||||
add(deleteBtn, gbc);
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
setMaximumSize(new Dimension(30, 300));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.widgets.list.GListCellRenderer;
|
||||
import ghidra.program.model.listing.FunctionTag;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
/**
|
||||
* Simple list for displaying {@link FunctionTag} items. The only part of the tag
|
||||
* displayed in the list is the name attribute.
|
||||
*/
|
||||
public class FunctionTagList extends JList<FunctionTag> {
|
||||
|
||||
public FunctionTagList(DefaultListModel<FunctionTag> model) {
|
||||
super(model);
|
||||
setCellRenderer(getCellRenderer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent evt) {
|
||||
int index = locationToIndex(evt.getPoint());
|
||||
if (index == -1 || index >= getModel().getSize()) {
|
||||
return "";
|
||||
}
|
||||
Object obj = getModel().getElementAt(index);
|
||||
if (obj instanceof FunctionTag) {
|
||||
FunctionTag tag = (FunctionTag) obj;
|
||||
|
||||
if (tag.getComment().isEmpty()) {
|
||||
return "<no comment set>";
|
||||
}
|
||||
|
||||
return "<html>" + HTMLUtilities.escapeHTML(tag.getComment());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom renderer for this list ensures that we only show the name attribute of
|
||||
* each {@link FunctionTag} object in the list.
|
||||
*
|
||||
* @return the cell renderer
|
||||
*/
|
||||
@Override
|
||||
public ListCellRenderer<? super FunctionTag> getCellRenderer() {
|
||||
return new GListCellRenderer<>() {
|
||||
@Override
|
||||
protected String getItemText(FunctionTag value) {
|
||||
return value.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends FunctionTag> list,
|
||||
FunctionTag value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
|
||||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
// If this tag is a temporary one (ie: read-in from a file), then it is
|
||||
// read-only and should be indicated to the user as a different color.
|
||||
if (value instanceof FunctionTagTemp) {
|
||||
if (cellHasFocus) {
|
||||
setForeground(Color.white);
|
||||
}
|
||||
else {
|
||||
setForeground(Color.blue);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -18,7 +18,8 @@ package ghidra.app.plugin.core.function.tags;
|
|||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
@ -58,6 +59,11 @@ public class FunctionTagPlugin extends ProgramPlugin {
|
|||
* PUBLIC METHODS
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Returns the component provider for this plugin
|
||||
*
|
||||
* @return the component provider
|
||||
*/
|
||||
public FunctionTagsComponentProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionTag;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
||||
/**
|
||||
* Table that displays function tags and a count of the number of times
|
||||
* each tag has been used
|
||||
*/
|
||||
public class FunctionTagTable extends GhidraTable {
|
||||
|
||||
/**
|
||||
* If true, disable any rows that have already been assigned
|
||||
* to a function (and thus cannot be added again)
|
||||
*/
|
||||
private boolean disable = false;
|
||||
|
||||
/** The selected function */
|
||||
private Function function = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param model the table model
|
||||
*/
|
||||
public FunctionTagTable(FunctionTagTableModel model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
protected void setDisabled(boolean disable) {
|
||||
this.disable = disable;
|
||||
}
|
||||
|
||||
public void setFunction(Function function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent evt) {
|
||||
FunctionTagTable table = (FunctionTagTable) evt.getSource();
|
||||
int row = this.rowAtPoint(evt.getPoint());
|
||||
int nameCol = table.getColumnModel().getColumnIndex("Name");
|
||||
String tagName = (String)table.getValueAt(row, nameCol);
|
||||
FunctionTagTableModel model = (FunctionTagTableModel) table.getModel();
|
||||
FunctionTag tag = model.getTag(tagName);
|
||||
|
||||
if (tag.getComment().isEmpty()) {
|
||||
return "no tooltip set";
|
||||
}
|
||||
|
||||
return "<html>" + HTMLUtilities.escapeHTML(tag.getComment());
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to override the renderer for the following cases:
|
||||
* <li>italicize tags that cannot be edited</li>
|
||||
* <li>disable rows in the source table that have already been added to the selected function </li>
|
||||
*/
|
||||
@Override
|
||||
public TableCellRenderer getCellRenderer(int row, int col) {
|
||||
|
||||
return new TableCellRenderer() {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
|
||||
DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
|
||||
Component c = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
boolean enableRow = true;
|
||||
if (disable && function != null) {
|
||||
int nameCol = table.getColumnModel().getColumnIndex("Name");
|
||||
String nameVal = (String)table.getValueAt(row, nameCol);
|
||||
Set<FunctionTag> tags = function.getTags();
|
||||
enableRow = !tags.stream().anyMatch(t -> t.getName().equals(nameVal));
|
||||
}
|
||||
c.setEnabled(enableRow);
|
||||
|
||||
switch (table.getColumnName(column)) {
|
||||
case "Count":
|
||||
break;
|
||||
case "Name":
|
||||
FunctionTagTableModel model = (FunctionTagTableModel) table.getModel();
|
||||
FunctionTag tag = model.getTag((String)value);
|
||||
if (tag instanceof FunctionTagTemp) {
|
||||
c.setFont(getFont().deriveFont(Font.ITALIC));
|
||||
}
|
||||
else {
|
||||
c.setFont(getFont().deriveFont(Font.PLAIN));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumnStub;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionIterator;
|
||||
import ghidra.program.model.listing.FunctionTag;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Model that backs a {@link FunctionTagTable}
|
||||
*/
|
||||
public class FunctionTagTableModel extends ThreadedTableModel<FunctionTag, List<FunctionTag>> {
|
||||
|
||||
/** The list of tags to display in the table */
|
||||
private List<FunctionTag> tags = new ArrayList<>();
|
||||
private Program program;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param modelName the name of this table model
|
||||
* @param serviceProvider the service provider
|
||||
*/
|
||||
protected FunctionTagTableModel(String modelName, ServiceProvider serviceProvider) {
|
||||
super(modelName, serviceProvider);
|
||||
}
|
||||
|
||||
public void setProgram(Program program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<FunctionTag> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
accumulator.addAll(tags);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<FunctionTag> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<FunctionTag> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new FunctionTagNameColumn());
|
||||
descriptor.addVisibleColumn(new FunctionTagCountColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FunctionTag> getDataSource() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a function tag to the table. If a tag with the same name is already
|
||||
* present in the table, does nothing.
|
||||
*
|
||||
* @param tag the function tag to add
|
||||
*/
|
||||
public void addTag(FunctionTag tag) {
|
||||
Optional<FunctionTag> existingTag = tags.stream()
|
||||
.filter(t -> t.getName().equals(tag.getName()))
|
||||
.findAny();
|
||||
if (existingTag.isPresent()) {
|
||||
tags.remove(existingTag.get());
|
||||
}
|
||||
|
||||
tags.add(tag);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all function tags from the model
|
||||
*/
|
||||
public void clear() {
|
||||
tags.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all function tags in the model
|
||||
*
|
||||
* @return all function tags
|
||||
*/
|
||||
public List<FunctionTag> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link FunctionTag} object with a given name
|
||||
*
|
||||
* @param name the tag name to search for
|
||||
* @return the function tag
|
||||
*/
|
||||
public FunctionTag getTag(String name) {
|
||||
Optional<FunctionTag> tag = tags.stream()
|
||||
.filter(t -> t.getName().equals(name))
|
||||
.findAny();
|
||||
if (tag.isPresent()) {
|
||||
return tag.get();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a function tag with a given name is in the model
|
||||
*
|
||||
* @param name the tag name to search fo
|
||||
* @return true if the tag exists in the model
|
||||
*/
|
||||
public boolean isTagInModel(String name) {
|
||||
Optional<FunctionTag> tag = tags.stream()
|
||||
.filter(t -> t.getName().equals(name))
|
||||
.findAny();
|
||||
return tag.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Table column that displays a count of the number of times a function tag has been
|
||||
* applied to a function (in the selected program)
|
||||
*/
|
||||
private class FunctionTagCountColumn extends AbstractDynamicTableColumnStub<FunctionTag, Integer> {
|
||||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return " "; // don't display any name, but need it to be at least one space
|
||||
// wide so the correct space is allocated to the header
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Count";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue(FunctionTag rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
int count = 0;
|
||||
|
||||
if (program == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
FunctionIterator iter = program.getFunctionManager().getFunctions(true);
|
||||
while (iter.hasNext()) {
|
||||
Function f = iter.next();
|
||||
Optional<FunctionTag> foundTag = f.getTags()
|
||||
.stream()
|
||||
.filter(t -> t.getName().equals(rowObject.getName()))
|
||||
.findAny();
|
||||
if (foundTag.isPresent()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Table column that displays the name of a function tag
|
||||
*/
|
||||
private class FunctionTagNameColumn extends AbstractDynamicTableColumnStub<FunctionTag, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Name";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(FunctionTag rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getName();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,8 +32,7 @@ import ghidra.framework.model.DomainObjectChangedEvent;
|
|||
import ghidra.framework.model.DomainObjectListener;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.*;
|
||||
|
||||
|
@ -60,6 +59,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
private SourceTagsPanel sourcePanel;
|
||||
private TargetTagsPanel targetPanel;
|
||||
private FunctionTagButtonPanel buttonPanel;
|
||||
private AllFunctionsPanel allFunctionsPanel;
|
||||
|
||||
private Program program;
|
||||
private JPanel mainPanel;
|
||||
|
@ -69,8 +69,8 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
private HintTextField tagInputTF;
|
||||
private HintTextField filterInputTF;
|
||||
|
||||
private int MIN_WIDTH = 400;
|
||||
private int MIN_HEIGHT = 150;
|
||||
private int MIN_WIDTH = 850;
|
||||
private int MIN_HEIGHT = 350;
|
||||
|
||||
// The current program location selected in the listing.
|
||||
private ProgramLocation currentLocation = null;
|
||||
|
@ -79,6 +79,12 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
// the create tag entry field.
|
||||
private static String INPUT_DELIMITER = ",";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param plugin the function tag plugin
|
||||
* @param program the current program
|
||||
*/
|
||||
public FunctionTagsComponentProvider(FunctionTagPlugin plugin, Program program) {
|
||||
super(plugin.getTool(), "Function Tags", plugin.getName(), ProgramActionContext.class);
|
||||
|
||||
|
@ -105,7 +111,6 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
updateTitle(currentLocation);
|
||||
|
||||
updateTagLists();
|
||||
});
|
||||
}
|
||||
|
@ -113,7 +118,6 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
@Override
|
||||
public void componentShown() {
|
||||
mainPanel = createWorkPanel();
|
||||
|
||||
updateTagLists();
|
||||
updateTitle(currentLocation);
|
||||
}
|
||||
|
@ -124,14 +128,13 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user has selected a new location in the listing. When
|
||||
* Invoked when a new location has been detected in the listing. When
|
||||
* this happens we need to update the tag list to show what tags are assigned
|
||||
* at the current location.
|
||||
*
|
||||
* @param loc the address selected in the listing
|
||||
*/
|
||||
public void locationChanged(ProgramLocation loc) {
|
||||
|
||||
currentLocation = loc;
|
||||
updateTitle(loc);
|
||||
updateTagLists();
|
||||
|
@ -159,7 +162,6 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
*/
|
||||
@Override
|
||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||
|
||||
if (ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_CHANGED) ||
|
||||
ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_CREATED) ||
|
||||
ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_DELETED) ||
|
||||
|
@ -183,8 +185,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
setSubTitle("NOT A FUNCTION");
|
||||
}
|
||||
else {
|
||||
setSubTitle(
|
||||
" " + function.getName() + " " + "(" + function.getEntryPoint().toString() + ")");
|
||||
setSubTitle("");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,53 +195,49 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
|
||||
// BOTTOM PANEL
|
||||
JPanel bottomPanel = new JPanel();
|
||||
bottomPanel.setLayout(new GridBagLayout());
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.weightx = 50;
|
||||
bottomPanel.add(createInputPanel(), gbc);
|
||||
|
||||
gbc.gridx = 1;
|
||||
bottomPanel.add(createFilterPanel(), gbc);
|
||||
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
|
||||
bottomPanel.add(createInputPanel());
|
||||
bottomPanel.add(createFilterPanel());
|
||||
|
||||
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||
mainPanel.setPreferredSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
|
||||
|
||||
// CENTER PANEL
|
||||
JPanel tagPanel = new JPanel();
|
||||
tagPanel.setLayout(new GridBagLayout());
|
||||
sourcePanel = new SourceTagsPanel(this, tool, "Available Tags");
|
||||
sourcePanel = new SourceTagsPanel(this, tool, "All Tags");
|
||||
targetPanel = new TargetTagsPanel(this, tool, "Assigned To Function");
|
||||
allFunctionsPanel = new AllFunctionsPanel(program, this, "Functions with Selected Tag");
|
||||
buttonPanel = new FunctionTagButtonPanel(sourcePanel, targetPanel);
|
||||
sourcePanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
|
||||
targetPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
|
||||
allFunctionsPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.weightx = 0.5;
|
||||
gbc.weighty = 1.0;
|
||||
gbc.fill = GridBagConstraints.BOTH;
|
||||
tagPanel.add(sourcePanel, gbc);
|
||||
// If we don't set this, then the splitter won't be able to shrink the
|
||||
// target panels below the size required by its header, which can be large
|
||||
// because of the amount of text displayed. Keep the minimum size setting on
|
||||
// the source panel, however. That is generally small.
|
||||
targetPanel.setMinimumSize(new Dimension(0, 0));
|
||||
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 0.0;
|
||||
tagPanel.add(buttonPanel, gbc);
|
||||
JPanel wrapper = new JPanel();
|
||||
wrapper.setLayout(new BoxLayout(wrapper, BoxLayout.X_AXIS));
|
||||
wrapper.add(sourcePanel);
|
||||
wrapper.add(buttonPanel);
|
||||
wrapper.add(targetPanel);
|
||||
|
||||
gbc.gridx = 2;
|
||||
gbc.weightx = 0.5;
|
||||
tagPanel.add(targetPanel, gbc);
|
||||
JSplitPane splitter =
|
||||
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, wrapper, allFunctionsPanel);
|
||||
|
||||
mainPanel.add(tagPanel, BorderLayout.CENTER);
|
||||
mainPanel.add(splitter, BorderLayout.CENTER);
|
||||
|
||||
splitter.setResizeWeight(0.5f);
|
||||
splitter.setDividerLocation(0.5f);
|
||||
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the button panel depending on the selection state of the
|
||||
* tag lists.
|
||||
* tag lists. Also updates the {@link AllFunctionsPanel} so it can update
|
||||
* its list.
|
||||
*
|
||||
* @param panel the panel that generated the selection event
|
||||
*/
|
||||
|
@ -256,6 +253,11 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
buttonPanel.targetPanelSelectionChanged(function != null);
|
||||
sourcePanel.clearSelection();
|
||||
}
|
||||
|
||||
List<FunctionTag> sourceTags = sourcePanel.getSelectedTags();
|
||||
List<FunctionTag> targetTags = targetPanel.getSelectedTags();
|
||||
sourceTags.addAll(targetTags);
|
||||
allFunctionsPanel.setSelectedTags(sourceTags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -309,11 +311,23 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
*/
|
||||
private void updateTagLists() {
|
||||
|
||||
if (sourcePanel == null || targetPanel == null) {
|
||||
if (sourcePanel == null || targetPanel == null || allFunctionsPanel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourcePanel.setProgram(program);
|
||||
targetPanel.setProgram(program);
|
||||
allFunctionsPanel.setProgram(program);
|
||||
|
||||
// Get the currently selected tags and use them to update the
|
||||
// all functions panel. If there is no current selection, leave the
|
||||
// table as-is.
|
||||
List<FunctionTag> sTags = sourcePanel.getSelectedTags();
|
||||
List<FunctionTag> tTags = targetPanel.getSelectedTags();
|
||||
sTags.addAll(tTags);
|
||||
if (!sTags.isEmpty()) {
|
||||
allFunctionsPanel.refresh(sTags);
|
||||
}
|
||||
|
||||
Function function = getFunctionAtLocation(currentLocation);
|
||||
sourcePanel.refresh(function);
|
||||
|
@ -397,7 +411,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter
|
|||
}
|
||||
});
|
||||
|
||||
filterPanel.add(new GLabel(" Filter:"), BorderLayout.WEST);
|
||||
filterPanel.add(new GLabel(" Tag Filter:"), BorderLayout.WEST);
|
||||
filterPanel.add(filterInputTF, BorderLayout.CENTER);
|
||||
|
||||
return filterPanel;
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import ghidra.app.cmd.function.AddFunctionTagCmd;
|
||||
import ghidra.app.cmd.function.CreateFunctionTagCmd;
|
||||
|
@ -53,20 +57,36 @@ public class SourceTagsPanel extends TagListPanel {
|
|||
// List of tags read in from the external file. These will be displayed in a different
|
||||
// color than 'regular' tags and cannot be edited or deleted, until they're added to a function
|
||||
private List<FunctionTag> tempTags = new ArrayList<>();
|
||||
|
||||
// Keeps a list of the original temp tags as loaded from file. This is necessary
|
||||
// when switching between programs where we need to know the original state of the
|
||||
// temporary tags. Without this we would need to reload from file on each new program
|
||||
// activation.
|
||||
private List<FunctionTag> tempTagsCache;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param provider the component provider
|
||||
* @param tool the plugin tool
|
||||
* @param title the title of the panel
|
||||
*/
|
||||
public SourceTagsPanel(FunctionTagsComponentProvider provider, PluginTool tool, String title) {
|
||||
super(provider, tool, title);
|
||||
|
||||
// Load any tags from external sources.
|
||||
tempTags = loadTags();
|
||||
// Load any tags from external sources and keep a copy in the cache
|
||||
tempTags = loadTags();
|
||||
tempTagsCache = new ArrayList<>(tempTags);
|
||||
|
||||
table.setDisabled(true);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* PUBLIC METHODS
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* Adds any selected to tags to the function currently selected in the
|
||||
* Adds any selected tags to the function currently selected in the
|
||||
* listing.
|
||||
*/
|
||||
public void addSelectedTags() {
|
||||
|
@ -90,74 +110,68 @@ public class SourceTagsPanel extends TagListPanel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void refresh(Function f) {
|
||||
public void refresh(Function function) {
|
||||
|
||||
model.clear();
|
||||
|
||||
this.function = f;
|
||||
|
||||
this.function = function;
|
||||
|
||||
try {
|
||||
tempTags = new ArrayList<>(tempTagsCache);
|
||||
|
||||
List<? extends FunctionTag> dbTags = getAllTagsFromDatabase();
|
||||
for (FunctionTag tag : dbTags) {
|
||||
model.addElement(tag);
|
||||
model.addTag(tag);
|
||||
}
|
||||
|
||||
// Now add any temp tags. Note that this is the point at which prune the
|
||||
|
||||
// Now add any temp tags. Note that this is the point at which we prune the
|
||||
// temp tag list to remove any tags that have been added to the database. We
|
||||
// don't do it when the command for the add has been initiated, we do it here,
|
||||
// in response to getting the latest items from the db directly.
|
||||
Iterator<FunctionTag> iter = tempTags.iterator();
|
||||
while (iter.hasNext()) {
|
||||
FunctionTag tag = iter.next();
|
||||
if (dbTags.contains(tag)) {
|
||||
Optional<? extends FunctionTag> foundTag = dbTags.stream()
|
||||
.filter(t -> t.getName().equals(tag.getName()))
|
||||
.findAny();
|
||||
if (foundTag.isPresent()) {
|
||||
iter.remove();
|
||||
}
|
||||
else {
|
||||
model.addElement(tag);
|
||||
model.addTag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
sortList();
|
||||
|
||||
model.reload();
|
||||
applyFilter();
|
||||
table.setFunction(function);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Error retrieving tags", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden because after applying the filter we need to then remove
|
||||
* all assigned items from this list (they will be in the target panel).
|
||||
* Returns true if all tags in the selection are enabled; false
|
||||
* otherwise
|
||||
*
|
||||
* @return true if all tags in the selection are enabled; false
|
||||
* otherwise
|
||||
*/
|
||||
@Override
|
||||
protected void applyFilter() {
|
||||
super.applyFilter();
|
||||
|
||||
if (function != null) {
|
||||
List<FunctionTag> assignedTags = getAssignedTags(function);
|
||||
for (FunctionTag tag : assignedTags) {
|
||||
removeTag(tag);
|
||||
}
|
||||
public boolean isSelectionEnabled() {
|
||||
List<FunctionTag> selectedTags = getSelectedTags();
|
||||
List<FunctionTag> assignedTags = getAssignedTags(function);
|
||||
if (assignedTags.containsAll(selectedTags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* PRIVATE METHODS
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Removes the given tag from this list.
|
||||
*
|
||||
* @param tag the tag to remove
|
||||
*/
|
||||
private void removeTag(FunctionTag tag) {
|
||||
for (int i = 0; i < filteredModel.size(); i++) {
|
||||
FunctionTag filteredTag = filteredModel.getElementAt(i);
|
||||
if (filteredTag.getName().equals(tag.getName())) {
|
||||
filteredModel.removeElement(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all tags stored in the database.
|
||||
*
|
||||
|
|
|
@ -18,49 +18,43 @@ package ghidra.app.plugin.core.function.tags;
|
|||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import docking.widgets.label.GLabel;
|
||||
import ghidra.app.cmd.function.ChangeFunctionTagCmd;
|
||||
import ghidra.app.cmd.function.DeleteFunctionTagCmd;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionTag;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Generic class for displaying {@link FunctionTag} objects in a list.
|
||||
* Base panel for displaying tags in the function tag window.
|
||||
*/
|
||||
public abstract class TagListPanel extends JPanel {
|
||||
|
||||
protected Program program;
|
||||
|
||||
// The list object containing the tag names.
|
||||
protected FunctionTagList list;
|
||||
|
||||
// Currently-selected function in the listing.
|
||||
protected Function function;
|
||||
|
||||
// Model representing all tags assigned to the selected function. This is the
|
||||
// complete, unfiltered set of tags.
|
||||
protected DefaultListModel<FunctionTag> model = new DefaultListModel<>();
|
||||
|
||||
// List of tags to be displayed in the panel. This is the full list with
|
||||
// filtering applied.
|
||||
protected DefaultListModel<FunctionTag> filteredModel = new DefaultListModel<>();
|
||||
|
||||
protected FunctionTagTable table;
|
||||
protected PluginTool tool;
|
||||
|
||||
protected String filterString = "";
|
||||
protected FunctionTagTableModel model;
|
||||
protected FunctionTagTableModel filteredModel;
|
||||
private JLabel titleLabel;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Constructor
|
||||
*
|
||||
* @param provider the display provider
|
||||
* @param tool the plugin tool
|
||||
|
@ -70,37 +64,43 @@ public abstract class TagListPanel extends JPanel {
|
|||
this.tool = tool;
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
model = new FunctionTagTableModel("", provider.getTool()) ;
|
||||
filteredModel = new FunctionTagTableModel("", provider.getTool());
|
||||
|
||||
table = new FunctionTagTable(filteredModel);
|
||||
table.addMouseListener(new MouseAdapter() {
|
||||
|
||||
// Set the model for the list to be the filtered model - we only ever want
|
||||
// to show the model that has filtering applied.
|
||||
list = new FunctionTagList(filteredModel);
|
||||
|
||||
// When a selection is made in the list, tell the provider so it can update
|
||||
// the state of buttons, the other list, etc...
|
||||
list.addListSelectionListener(e -> {
|
||||
if (!e.getValueIsAdjusting()) {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
// Click events aren't reliably captured for some reason,
|
||||
// but presses are, so this is the best way to ensure that
|
||||
// user selections are handled
|
||||
provider.selectionChanged(TagListPanel.this);
|
||||
}
|
||||
});
|
||||
|
||||
// Mouse listener for handling the double-click event, which will bring up
|
||||
// a dialog for editing the tag name and/or comment.
|
||||
list.addMouseListener(new MouseAdapter() {
|
||||
// Handles the double-click event on table rows, which will bring up
|
||||
// a dialog for editing the tag name and/or comment.
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent evt) {
|
||||
FunctionTagList list = (FunctionTagList) evt.getSource();
|
||||
|
||||
FunctionTagTable table = (FunctionTagTable)evt.getSource();
|
||||
|
||||
if (evt.getClickCount() == 2) {
|
||||
|
||||
FunctionTag tag = list.getSelectedValue();
|
||||
int row = table.getSelectedRow();
|
||||
int nameCol = table.getColumnModel().getColumnIndex("Name");
|
||||
String tagName = (String) table.getValueAt(row, nameCol);
|
||||
FunctionTagTableModel model = (FunctionTagTableModel) table.getModel();
|
||||
FunctionTag tag = model.getTag(tagName);
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the tag is a temporary one, it's not editable. Show a message to the user.
|
||||
if (tag instanceof FunctionTagTemp) {
|
||||
Msg.showWarn(list, list, "Tag Not Editable", "Tag " + "\"" + tag.getName() +
|
||||
Msg.showWarn(this, table, "Tag Not Editable", "Tag " + "\"" + tag.getName() +
|
||||
"\"" +
|
||||
" was loaded from an external source and cannot be edited or deleted");
|
||||
" must be added to the program before it can be modified/deleted");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ public abstract class TagListPanel extends JPanel {
|
|||
// If the name is empty, show a warning and don't allow it. A user should
|
||||
// never want to do this.
|
||||
if (newName.isEmpty()) {
|
||||
Msg.showWarn(this, list, "Empty Tag Name?", "Tag name cannot be empty");
|
||||
Msg.showWarn(this, table, "Empty Tag Name?", "Tag name cannot be empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -141,8 +141,7 @@ public abstract class TagListPanel extends JPanel {
|
|||
return true;
|
||||
});
|
||||
|
||||
dialog.setPreferredSize(400, 150);
|
||||
DockingWindowManager.showDialog(list, dialog);
|
||||
DockingWindowManager.showDialog(tool.getActiveWindow(), dialog);
|
||||
|
||||
if (dialog.isCanceled()) {
|
||||
return;
|
||||
|
@ -150,17 +149,19 @@ public abstract class TagListPanel extends JPanel {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
add(new GLabel(title), BorderLayout.NORTH);
|
||||
add(list, BorderLayout.CENTER);
|
||||
|
||||
titleLabel = new JLabel(title);
|
||||
titleLabel.setBorder(BorderFactory.createEmptyBorder(3, 5, 0, 0));
|
||||
add(titleLabel, BorderLayout.NORTH);
|
||||
add(new JScrollPane(table), BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* PUBLIC METHODS
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Clears the list and repopulates with a new data set. Clients should override this
|
||||
* Clears the list and repopulates it with a new data set. Clients should override this
|
||||
* to retrieve data for the given function.
|
||||
*
|
||||
* @param function the currently selected function in the listing
|
||||
|
@ -168,11 +169,13 @@ public abstract class TagListPanel extends JPanel {
|
|||
public abstract void refresh(Function function);
|
||||
|
||||
public void clearSelection() {
|
||||
list.clearSelection();
|
||||
table.clearSelection();
|
||||
}
|
||||
|
||||
public void setProgram(Program program) {
|
||||
this.program = program;
|
||||
model.setProgram(program);
|
||||
filteredModel.setProgram(program);
|
||||
}
|
||||
|
||||
public void setFilterText(String text) {
|
||||
|
@ -180,6 +183,10 @@ public abstract class TagListPanel extends JPanel {
|
|||
applyFilter();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
titleLabel.setText(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the tag already exists in the model.
|
||||
*
|
||||
|
@ -187,14 +194,7 @@ public abstract class TagListPanel extends JPanel {
|
|||
* @return true if the tag exists
|
||||
*/
|
||||
public boolean tagExists(String name) {
|
||||
for (int i = 0; i < model.size(); i++) {
|
||||
FunctionTag tag = model.getElementAt(i);
|
||||
if (tag.getName().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return model.isTagInModel(name);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
|
@ -207,27 +207,27 @@ public abstract class TagListPanel extends JPanel {
|
|||
* @return true if the list has an item selected
|
||||
*/
|
||||
protected boolean hasSelection() {
|
||||
return list.getSelectedIndices().length != 0;
|
||||
return table.getSelectedRowCount() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one of the selected items in the list
|
||||
* is immutable (a temporary non-user-defined tag that can't be deleted).
|
||||
* is immutable (a temporary non-user-defined tag that can't be edited/deleted).
|
||||
*
|
||||
* @return true if list contains an immutable tag
|
||||
*/
|
||||
protected boolean isSelectionImmutable() {
|
||||
return list.getSelectedValuesList().stream().anyMatch(
|
||||
val -> val instanceof FunctionTagTemp);
|
||||
}
|
||||
|
||||
protected void sortList() {
|
||||
List<FunctionTag> myList = Collections.list(model.elements());
|
||||
Collections.sort(myList);
|
||||
model.clear();
|
||||
for (FunctionTag tag : myList) {
|
||||
model.addElement(tag);
|
||||
int[] selectedRows = table.getSelectedRows();
|
||||
int nameCol = table.getColumnModel().getColumnIndex("Name");
|
||||
for (int i=0; i<selectedRows.length; i++) {
|
||||
String tagName = (String) table.getValueAt(i, nameCol);
|
||||
FunctionTag tag = filteredModel.getTag(tagName);
|
||||
if (tag instanceof FunctionTagTemp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,41 +261,51 @@ public abstract class TagListPanel extends JPanel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Filters the list with the current filter settings.
|
||||
* Filters the list with the current filter settings
|
||||
*/
|
||||
protected void applyFilter() {
|
||||
filteredModel.clear();
|
||||
|
||||
for (int i = 0; i < model.size(); i++) {
|
||||
if (model.get(i).getName().toLowerCase().contains(filterString.toLowerCase())) {
|
||||
filteredModel.addElement(model.get(i));
|
||||
|
||||
for (FunctionTag tag : model.getTags()) {
|
||||
if (filterString.isEmpty()) {
|
||||
filteredModel.addTag(tag);
|
||||
}
|
||||
else if (tag.getName().toLowerCase().contains(filterString.toLowerCase())) {
|
||||
filteredModel.addTag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
filteredModel.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all tags that have been assigned to the given function.
|
||||
* Retrieves all tags that have been assigned to the given function
|
||||
*
|
||||
* @param func the function to get tags for
|
||||
* @return list of all tags assigned to this function
|
||||
*/
|
||||
protected List<FunctionTag> getAssignedTags(Function function) {
|
||||
protected List<FunctionTag> getAssignedTags(Function func) {
|
||||
List<FunctionTag> assignedTags = new ArrayList<>();
|
||||
if (function != null) {
|
||||
assignedTags.addAll(function.getTags());
|
||||
if (func != null) {
|
||||
assignedTags.addAll(func.getTags());
|
||||
}
|
||||
return assignedTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all tags selected in the list.
|
||||
* Returns a list of all tags selected in the list
|
||||
*
|
||||
* @return the list of function tags
|
||||
*/
|
||||
protected List<FunctionTag> getSelectedTags() {
|
||||
List<FunctionTag> tags = new ArrayList<>();
|
||||
int[] selectedIndices = list.getSelectedIndices();
|
||||
int[] selectedIndices = table.getSelectedRows();
|
||||
for (int i : selectedIndices) {
|
||||
tags.add(filteredModel.getElementAt(i));
|
||||
String tagName = (String) filteredModel.getValueAt(i, 0);
|
||||
Optional<FunctionTag> tag = filteredModel.getTags().stream().filter(t -> t.getName().equals(tagName)).findAny();
|
||||
if (tag.isPresent()) {
|
||||
tags.add(tag.get());
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
|
|
|
@ -26,14 +26,22 @@ import ghidra.program.model.listing.FunctionTag;
|
|||
|
||||
/**
|
||||
* Displays a list of tags that have been assigned to the currently-selected
|
||||
* function in the listing. If no function is selected, the list will be
|
||||
* empty.
|
||||
* function in the listing
|
||||
*/
|
||||
public class TargetTagsPanel extends TagListPanel {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param provider the component provider
|
||||
* @param tool the plugin tool
|
||||
* @param title the panel title
|
||||
*/
|
||||
public TargetTagsPanel(FunctionTagsComponentProvider provider,
|
||||
PluginTool tool, String title) {
|
||||
super(provider, tool, title);
|
||||
|
||||
table.setDisabled(false);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
|
@ -42,18 +50,27 @@ public class TargetTagsPanel extends TagListPanel {
|
|||
|
||||
@Override
|
||||
public void refresh(Function function) {
|
||||
|
||||
model.clear();
|
||||
|
||||
this.function = function;
|
||||
|
||||
if (function == null) {
|
||||
setTitle("No Function Selected");
|
||||
}
|
||||
else {
|
||||
setTitle(function.getName() + " " + "(" + function.getEntryPoint().toString() + ")");
|
||||
}
|
||||
|
||||
List<FunctionTag> assignedTags = getAssignedTags(function);
|
||||
Collections.sort(assignedTags);
|
||||
for (FunctionTag tag : assignedTags) {
|
||||
model.addElement(tag);
|
||||
model.addTag(tag);
|
||||
}
|
||||
|
||||
this.function = function;
|
||||
|
||||
sortList();
|
||||
|
||||
model.reload();
|
||||
applyFilter();
|
||||
table.setFunction(function);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,15 +15,23 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.function.tags;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
|
@ -37,10 +45,13 @@ import ghidra.framework.plugintool.PluginTool;
|
|||
import ghidra.program.database.function.FunctionDB;
|
||||
import ghidra.program.database.function.FunctionManagerDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionTag;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.test.*;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.ClassicSampleX86ProgramBuilder;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.exception.UsrException;
|
||||
|
||||
/**
|
||||
|
@ -63,12 +74,10 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
// dialog.
|
||||
private DockingActionIf editFunctionTags;
|
||||
|
||||
private static final int DIALOG_WAIT_TIME = 3000;
|
||||
|
||||
// Define addresses for the first two functions in the test program;
|
||||
// these will handle most cases we need to test.
|
||||
private Address NON_FUNCTION_ADDRESS;
|
||||
private Address FUNCTION_ENTRY_ADDRESS;
|
||||
private Address FUNCTION_ENTRY_ADDRESS_2;
|
||||
private Address FUNCTION_ENTRY_ADDRESS_3;
|
||||
|
||||
// The UI we're testing.
|
||||
private FunctionTagsComponentProvider provider = null;
|
||||
|
@ -99,6 +108,8 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
NON_FUNCTION_ADDRESS = addr("010022b8");
|
||||
FUNCTION_ENTRY_ADDRESS = addr("01002239");
|
||||
FUNCTION_ENTRY_ADDRESS_2 = addr("0100248f");
|
||||
FUNCTION_ENTRY_ADDRESS_3 = addr("0100299e");
|
||||
|
||||
goToFunction(FUNCTION_ENTRY_ADDRESS);
|
||||
showDialog();
|
||||
|
@ -144,20 +155,18 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
* non-immutable tags to the selection</li>
|
||||
* <li>Verify that the delete button will become enabled if we
|
||||
* remove any immutable tags from the selection</li>
|
||||
*
|
||||
* @throws UsrException if there's an error retrieving tag panel components
|
||||
* @throws IOException if there's an error retrieving tags from the database
|
||||
* @throws Exception if there is a problem selecting tags
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteImmutableTag() throws IOException, UsrException {
|
||||
public void testDeleteImmutableTag() throws Exception {
|
||||
|
||||
FunctionTagList list = getTagListInPanel("sourcePanel");
|
||||
FunctionTagTable table = getTagListInPanel("sourcePanel");
|
||||
|
||||
// Get an immutable tag from the source panel and set it to be selected. Verify that
|
||||
// the delete button is disabled.
|
||||
FunctionTagTemp immutableTag = getImmutableTag();
|
||||
assertTrue("Must have at least one immutable tag for this test", immutableTag != null);
|
||||
selectTagInList(immutableTag.getName(), list);
|
||||
selectTagInList(immutableTag.getName(), table);
|
||||
waitForSwing();
|
||||
assertFalse(isButtonEnabled("deleteBtn"));
|
||||
|
||||
|
@ -166,13 +175,13 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
String tagName = "TAG 1";
|
||||
createTag(tagName);
|
||||
waitForSwing();
|
||||
list.setSelectionInterval(0, list.getModel().getSize() - 1);
|
||||
table.addRowSelectionInterval(0, table.getRowCount()-1);
|
||||
waitForSwing();
|
||||
assertFalse(isButtonEnabled("deleteBtn"));
|
||||
|
||||
// Select just the non-immutable tag and verify that the delete button is now
|
||||
// enabled.
|
||||
selectTagInList(tagName, list);
|
||||
selectTagInList(tagName, table);
|
||||
waitForSwing();
|
||||
assertTrue(isButtonEnabled("deleteBtn"));
|
||||
}
|
||||
|
@ -180,14 +189,12 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
/**
|
||||
* Verifies that we can delete a previously immutable tag once it has been assigned
|
||||
* to a function.
|
||||
*
|
||||
* @throws UsrException if there's an error retrieving tag panel components
|
||||
* @throws IOException if there's an error retrieving tags from the database
|
||||
* @throws Exception if there is a problem adding tags to functions
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteImmutableTagAfterUse() throws UsrException, IOException {
|
||||
public void testDeleteImmutableTagAfterUse() throws Exception {
|
||||
|
||||
FunctionTagList list = getTagListInPanel("targetPanel");
|
||||
FunctionTagTable table = getTagListInPanel("targetPanel");
|
||||
|
||||
// Get an immutable tag from the source panel.
|
||||
FunctionTagTemp tag = getImmutableTag();
|
||||
|
@ -201,7 +208,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
boolean inList = isTagNameInList(tag.getName(), getAllTags());
|
||||
assertTrue(inList);
|
||||
|
||||
selectTagInList(tag.getName(), list);
|
||||
selectTagInList(tag.getName(), table);
|
||||
waitForSwing();
|
||||
assertTrue(isButtonEnabled("deleteBtn"));
|
||||
}
|
||||
|
@ -282,11 +289,11 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
/**
|
||||
* Tests that we can add a tag to a function.
|
||||
*
|
||||
* @throws UsrException if there's an error adding the tag from the function
|
||||
*
|
||||
* @throws Exception if there is a problem adding tags to functions
|
||||
*/
|
||||
@Test
|
||||
public void testAddTagToFunction() throws UsrException {
|
||||
public void testAddTagToFunction() throws Exception {
|
||||
String name = "TEST";
|
||||
createTag(name);
|
||||
addTagToFunction(name, FUNCTION_ENTRY_ADDRESS);
|
||||
|
@ -295,11 +302,10 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
/**
|
||||
* Tests that we can remove a tag from a function.
|
||||
*
|
||||
* @throws UsrException if there's an error adding or removing the tag from the function
|
||||
* @throws Exception if there is a problem adding/removing tags to functions
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveTagFromFunction() throws UsrException {
|
||||
public void testRemoveTagFromFunction() throws Exception {
|
||||
String name = "TEST";
|
||||
createTag(name);
|
||||
addTagToFunction(name, FUNCTION_ENTRY_ADDRESS);
|
||||
|
@ -307,6 +313,120 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertTrue(!isTagNameInList(name, getTagsForFunctionAt(FUNCTION_ENTRY_ADDRESS)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the tags assigned to a function are visible in the function tag
|
||||
* panel
|
||||
* @throws Exception if there is a problem adding tags to functions
|
||||
*/
|
||||
@Test
|
||||
public void testViewFunctionsForTag() throws Exception {
|
||||
|
||||
// Verify that the function panel is initially empty
|
||||
AllFunctionsPanel functionsPanel = getFunctionsPanel();
|
||||
List<Function> functions = functionsPanel.getFunctions();
|
||||
assert (functions.isEmpty());
|
||||
|
||||
// Create a new tag and add it to a function
|
||||
String tagName1 = "TAG 1";
|
||||
createTag(tagName1);
|
||||
addTagToFunction(tagName1, FUNCTION_ENTRY_ADDRESS);
|
||||
|
||||
// Select the tag in the target panel
|
||||
FunctionTagTable table = getTagListInPanel("targetPanel");
|
||||
selectTagInList(tagName1, table);
|
||||
waitForTableModel(functionsPanel.getTableModel());
|
||||
|
||||
// Verify that the function is shown in the function panel (check that
|
||||
// we have exactly 1 match, and that the address is for the correct
|
||||
// function)
|
||||
functions = functionsPanel.getFunctions();
|
||||
assert (functions.size() == 1);
|
||||
Function f = functions.get(0);
|
||||
assert (f.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if a tag is assigned to multiple functions they will all be shown
|
||||
* in the function panel
|
||||
*
|
||||
* @throws Exception if there's a problem adding tags to functions
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleFunctionsWithTag() throws Exception {
|
||||
|
||||
// Verify that the function panel is initially empty
|
||||
AllFunctionsPanel functionsPanel = getFunctionsPanel();
|
||||
List<Function> functions = functionsPanel.getFunctions();
|
||||
assert (functions.isEmpty());
|
||||
|
||||
// Create a new tag and add it to both functions
|
||||
String tagName1 = "TAG 1";
|
||||
createTag(tagName1);
|
||||
addTagToFunction(tagName1, FUNCTION_ENTRY_ADDRESS);
|
||||
addTagToFunction(tagName1, FUNCTION_ENTRY_ADDRESS_2);
|
||||
|
||||
// Select the tag in the target panel
|
||||
FunctionTagTable table = getTagListInPanel("targetPanel");
|
||||
selectTagInList(tagName1, table);
|
||||
waitForTableModel(functionsPanel.getTableModel());
|
||||
|
||||
// Verify that both functions are shown in the function panel (check that
|
||||
// we have exactly 2 matches, and that the addresses are for the correct
|
||||
// functions)
|
||||
functions = functionsPanel.getFunctions();
|
||||
assert (functions.size() == 2);
|
||||
Function f1 = functions.get(0);
|
||||
Function f2 = functions.get(1);
|
||||
assert (f1.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS));
|
||||
assert (f2.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if multiple tags are selected, all functions containing that tag
|
||||
* are displayed
|
||||
*
|
||||
* @throws Exception if there's a problem adding tags to functions
|
||||
*/
|
||||
@Test
|
||||
public void testViewMultipleFunctions() throws Exception {
|
||||
|
||||
// Verify that the function panel is initially empty
|
||||
AllFunctionsPanel functionsPanel = getFunctionsPanel();
|
||||
List<Function> functions = functionsPanel.getFunctions();
|
||||
assert (functions.isEmpty());
|
||||
|
||||
// Create two new tags and add them to the functions
|
||||
String tagName1 = "TAG 1";
|
||||
createTag(tagName1);
|
||||
addTagToFunction(tagName1, FUNCTION_ENTRY_ADDRESS);
|
||||
|
||||
String tagName2 = "TAG 2";
|
||||
createTag(tagName2);
|
||||
addTagToFunction(tagName2, FUNCTION_ENTRY_ADDRESS_2);
|
||||
addTagToFunction(tagName2, FUNCTION_ENTRY_ADDRESS_3);
|
||||
|
||||
// Select both tags and verify that 3 functions are listed in
|
||||
// the functions panel
|
||||
goTo(tool, program, addr("00000000"));
|
||||
FunctionTagTable table = getTagListInPanel("sourcePanel");
|
||||
selectTagInList(tagName1, table);
|
||||
int index = table.getSelectedRow();
|
||||
table.addRowSelectionInterval(index, index+1);
|
||||
clickTableRange(table, index, 2);
|
||||
|
||||
waitForTableModel(functionsPanel.getTableModel());
|
||||
|
||||
// Verify that all 3 functions are in the function panel
|
||||
functions = functionsPanel.getFunctions();
|
||||
assert (functions.size() == 3);
|
||||
Function f1 = functions.get(0);
|
||||
Function f2 = functions.get(1);
|
||||
Function f3 = functions.get(2);
|
||||
assert (f1.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS));
|
||||
assert (f2.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_2));
|
||||
assert (f3.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_3));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* PRIVATE METHODS
|
||||
****************************************************************************************/
|
||||
|
@ -333,14 +453,14 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
*
|
||||
* @param name the tag name to add
|
||||
* @param address the function entry point
|
||||
* @throws UsrException if there's an error retrieving the source panel instance
|
||||
* @throws Exception if there is a problem selecting tags or clicking buttons
|
||||
*/
|
||||
private void addTagToFunction(String name, Address address) throws UsrException {
|
||||
private void addTagToFunction(String name, Address address) throws Exception {
|
||||
|
||||
cb.goTo(new ProgramLocation(program, address));
|
||||
waitForSwing();
|
||||
|
||||
FunctionTagList list = getTagListInPanel("sourcePanel");
|
||||
FunctionTagTable list = getTagListInPanel("sourcePanel");
|
||||
selectTagInList(name, list);
|
||||
clickButtonByName("addBtn");
|
||||
}
|
||||
|
@ -350,54 +470,74 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
*
|
||||
* @param name the tag name to remove
|
||||
* @param address the function entry point
|
||||
* @throws UsrException if there's an error retrieving the target panel instance
|
||||
* @throws Exception if there is a problem selecting tags or clicking buttons
|
||||
*/
|
||||
private void removeTagFromFunction(String name, Address address) throws UsrException {
|
||||
private void removeTagFromFunction(String name, Address address) throws Exception {
|
||||
|
||||
cb.goTo(new ProgramLocation(program, address));
|
||||
waitForSwing();
|
||||
|
||||
FunctionTagList list = getTagListInPanel("targetPanel");
|
||||
selectTagInList(name, list);
|
||||
FunctionTagTable table = getTagListInPanel("targetPanel");
|
||||
selectTagInList(name, table);
|
||||
clickButtonByName("removeBtn");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instance of the tag list in the given panel.
|
||||
* Gets the instance of the tag table in the given panel.
|
||||
*
|
||||
* @param panelName the name of the panel to search
|
||||
* @return the function tag list
|
||||
* @throws UsrException if there's an error retrieving the panel or tag list instances
|
||||
*/
|
||||
private FunctionTagList getTagListInPanel(String panelName) throws UsrException {
|
||||
private FunctionTagTable getTagListInPanel(String panelName) throws UsrException {
|
||||
Object comp = getInstanceField(panelName, provider);
|
||||
if (comp == null) {
|
||||
throw new UsrException("Error getting targetPanel field in provider");
|
||||
}
|
||||
|
||||
TagListPanel panel = (TagListPanel) comp;
|
||||
Object list = getInstanceField("list", panel);
|
||||
if (list == null) {
|
||||
throw new UsrException("Error getting list field in TargetTagsPanel");
|
||||
Object table = getInstanceField("table", comp);
|
||||
if (table == null) {
|
||||
throw new UsrException("Error getting table in tag panel");
|
||||
}
|
||||
|
||||
FunctionTagList tagList = (FunctionTagList) list;
|
||||
return tagList;
|
||||
FunctionTagTable tagTable = (FunctionTagTable) table;
|
||||
return tagTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the item in the list with the given name.
|
||||
* Returns the functions panel
|
||||
*
|
||||
* @return the functions panel
|
||||
*/
|
||||
private AllFunctionsPanel getFunctionsPanel() {
|
||||
AllFunctionsPanel panel =
|
||||
(AllFunctionsPanel) getInstanceField("allFunctionsPanel", provider);
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the item in the table with the given name.
|
||||
*
|
||||
* @param name the tag name to select
|
||||
* @param list the list to search
|
||||
* @throws UsrException if there's an error retrieving tag from the list
|
||||
* @param table the table to search
|
||||
* @throws Exception if there is a problem clicking cells in a list
|
||||
*/
|
||||
private void selectTagInList(String name, FunctionTagList list) throws UsrException {
|
||||
FunctionTag tag = getListItemByName(name, list);
|
||||
private void selectTagInList(String name, FunctionTagTable table) throws Exception {
|
||||
FunctionTag tag = getListItemByName(name, table);
|
||||
if (tag == null) {
|
||||
throw new UsrException("Error retrieving tag with name: " + name);
|
||||
}
|
||||
list.setSelectedValue(tag, true);
|
||||
|
||||
int row = 0;
|
||||
for (int i=0; i<table.getRowCount(); i++) {
|
||||
String tagname = (String) table.getValueAt(i, 0);
|
||||
if (tagname.equals(name)) {
|
||||
table.addRowSelectionInterval(i,i);
|
||||
row = i;
|
||||
}
|
||||
}
|
||||
|
||||
clickTableCell(table, row, 0, 1);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
|
@ -438,12 +578,13 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
* Returns the list item (FunctionTag) that has the given tag name.
|
||||
*
|
||||
* @param name the tag name
|
||||
* @param list the list to search
|
||||
* @param table the table to search
|
||||
*/
|
||||
private FunctionTag getListItemByName(String name, FunctionTagList list) {
|
||||
int count = list.getModel().getSize();
|
||||
private FunctionTag getListItemByName(String name, FunctionTagTable table) {
|
||||
int count = table.getRowCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
FunctionTag tag = list.getModel().getElementAt(i);
|
||||
FunctionTagTableModel model = (FunctionTagTableModel)table.getModel();
|
||||
FunctionTag tag = model.getTag(name);
|
||||
if (tag.getName().equals(name)) {
|
||||
return tag;
|
||||
}
|
||||
|
@ -467,14 +608,10 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
/**
|
||||
* Displays the function tag dialog.
|
||||
*
|
||||
* @return the dialog component provider
|
||||
*/
|
||||
private void showDialog() {
|
||||
|
||||
performAction(editFunctionTags, cb.getProvider(), false);
|
||||
provider = waitForComponentProvider(tool.getToolFrame(),
|
||||
FunctionTagsComponentProvider.class, DIALOG_WAIT_TIME);
|
||||
provider = waitForComponentProvider(FunctionTagsComponentProvider.class);
|
||||
tool.showComponentProvider(provider, true);
|
||||
}
|
||||
|
||||
|
@ -549,13 +686,11 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
*/
|
||||
private FunctionTagTemp getImmutableTag() throws UsrException {
|
||||
|
||||
FunctionTagList list = getTagListInPanel("sourcePanel");
|
||||
|
||||
for (int i = 0; i < list.getModel().getSize(); i++) {
|
||||
FunctionTag tag = list.getModel().getElementAt(i);
|
||||
if (tag instanceof FunctionTagTemp) {
|
||||
return (FunctionTagTemp) tag;
|
||||
}
|
||||
FunctionTagTable table = getTagListInPanel("sourcePanel");
|
||||
FunctionTagTableModel model = (FunctionTagTableModel)table.getModel();
|
||||
Optional<FunctionTag> foundTag = model.getTags().stream().filter(t -> t instanceof FunctionTagTemp).findAny();
|
||||
if (foundTag.isPresent()) {
|
||||
return (FunctionTagTemp)foundTag.get();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package generic.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
@ -1296,6 +1296,46 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
|||
waitForSwing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks a range of items in a list (simulates holding SHIFT and selecting
|
||||
* each item in the range in-turn)
|
||||
*
|
||||
* @param list the list to select from
|
||||
* @param row the initial index
|
||||
* @param count the number of rows to select
|
||||
* @throws Exception if there's a problem simulating the click
|
||||
*/
|
||||
public static void clickListRange(final JList list, final int row, int count)
|
||||
throws Exception {
|
||||
waitForSwing();
|
||||
for (int i = row; i < row + count; i++) {
|
||||
Rectangle rect = list.getCellBounds(i, i);
|
||||
clickMouse(list, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1,
|
||||
InputEvent.SHIFT_DOWN_MASK);
|
||||
}
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks a range of items in a table (simulates holding SHIFT and selecting
|
||||
* each item in the range)
|
||||
*
|
||||
* @param table the table to select
|
||||
* @param row the starting row index
|
||||
* @param count the number of rows to select
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void clickTableRange(final JTable table, final int row, int count)
|
||||
throws Exception {
|
||||
waitForSwing();
|
||||
for (int i = row; i < row + count; i++) {
|
||||
Rectangle rect = table.getCellRect(i, 0, true);
|
||||
clickMouse(table, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1,
|
||||
InputEvent.SHIFT_DOWN_MASK);
|
||||
}
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
public static TableCellEditor editCell(final JTable table, final int row, final int col) {
|
||||
|
||||
waitForSwing();
|
||||
|
|
|
@ -19,15 +19,21 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import ghidra.app.plugin.core.function.tags.*;
|
||||
import ghidra.app.plugin.core.function.tags.FunctionTagButtonPanel;
|
||||
import ghidra.app.plugin.core.function.tags.FunctionTagTableModel;
|
||||
import ghidra.app.plugin.core.function.tags.FunctionTagTable;
|
||||
import ghidra.app.plugin.core.function.tags.FunctionTagsComponentProvider;
|
||||
import ghidra.app.plugin.core.function.tags.SourceTagsPanel;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionIterator;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.exception.UsrException;
|
||||
|
||||
|
@ -38,7 +44,7 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
showProvider(FunctionTagsComponentProvider.class);
|
||||
waitForSwing();
|
||||
addTableData();
|
||||
captureIsolatedProvider(FunctionTagsComponentProvider.class, 400, 300);
|
||||
captureIsolatedProvider(FunctionTagsComponentProvider.class, 950, 400);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -71,9 +77,9 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
|
||||
FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class);
|
||||
SourceTagsPanel sourcePanel = (SourceTagsPanel) getInstanceField("sourcePanel", provider);
|
||||
FunctionTagList list = (FunctionTagList) getInstanceField("list", sourcePanel);
|
||||
Rectangle bounds = list.getCellBounds(7, 7); // Cell 7 is an editable item
|
||||
doubleClick(list, bounds.x, bounds.y);
|
||||
FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel);
|
||||
Rectangle bounds = table.getCellRect(7, 0, false); // Cell 7 is an editable item
|
||||
doubleClick(table, bounds.x, bounds.y);
|
||||
|
||||
InputDialog warningDialog = waitForDialogComponent(InputDialog.class);
|
||||
|
||||
|
@ -95,8 +101,8 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
|
||||
FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class);
|
||||
SourceTagsPanel sourcePanel = (SourceTagsPanel) getInstanceField("sourcePanel", provider);
|
||||
FunctionTagList list = (FunctionTagList) getInstanceField("list", sourcePanel);
|
||||
list.setSelectedIndex(7);
|
||||
FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel);
|
||||
table.setRowSelectionInterval(7, 7);
|
||||
FunctionTagButtonPanel buttonPanel =
|
||||
(FunctionTagButtonPanel) getInstanceField("buttonPanel", provider);
|
||||
pressButtonByName(buttonPanel, "deleteBtn", false);
|
||||
|
@ -117,8 +123,8 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
|
||||
FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class);
|
||||
SourceTagsPanel sourcePanel = (SourceTagsPanel) getInstanceField("sourcePanel", provider);
|
||||
FunctionTagList list = (FunctionTagList) getInstanceField("list", sourcePanel);
|
||||
doubleClickItem(list, "LIBRARY"); // pick a known read-only tag
|
||||
FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel);
|
||||
doubleClickItem(table, "LIBRARY"); // pick a known read-only tag
|
||||
|
||||
OptionDialog warningDialog = waitForDialogComponent(OptionDialog.class);
|
||||
captureDialog(warningDialog);
|
||||
|
@ -128,15 +134,13 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void doubleClickItem(FunctionTagList list, String text) {
|
||||
private void doubleClickItem(FunctionTagTable table, String text) {
|
||||
|
||||
ListModel<FunctionTag> model = list.getModel();
|
||||
int n = model.getSize();
|
||||
FunctionTagTableModel model = (FunctionTagTableModel) table.getModel();
|
||||
int row = -1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
FunctionTag tag = model.getElementAt(i);
|
||||
String name = tag.getName();
|
||||
if (text.equals(name)) {
|
||||
for (int i=0; i<model.getRowCount(); i++) {
|
||||
String name = (String) table.getValueAt(i, 0);
|
||||
if (name.equals(text)) {
|
||||
row = i;
|
||||
break;
|
||||
}
|
||||
|
@ -144,8 +148,8 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
|
||||
assertTrue("Could not find tag '" + text + "'", row > -1);
|
||||
|
||||
Rectangle bounds = list.getCellBounds(row, row);
|
||||
doubleClick(list, bounds.x, bounds.y);
|
||||
Rectangle bounds = table.getCellRect(row, 0, true);
|
||||
doubleClick(table, bounds.x, bounds.y);
|
||||
}
|
||||
|
||||
private void addTableData() {
|
||||
|
|