GT-3318 - Plugin Description display was not correctly using html tags

This commit is contained in:
dragonmacher 2019-11-14 18:25:02 -05:00
parent 719841eb20
commit 5007c000dc
4 changed files with 94 additions and 154 deletions

View file

@ -15,15 +15,14 @@
*/
package ghidra.framework.plugintool.dialog;
import static ghidra.util.HTMLUtilities.*;
import java.awt.*;
import java.util.StringTokenizer;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.label.GDHtmlLabel;
import ghidra.util.HTMLUtilities;
@ -34,6 +33,7 @@ import ghidra.util.HTMLUtilities;
*/
public abstract class AbstractDetailsPanel extends JPanel {
private static final int MIN_WIDTH = 700;
protected static final int LEFT_COLUMN_WIDTH = 150;
protected static final int RIGHT_MARGIN = 30;
@ -41,7 +41,6 @@ public abstract class AbstractDetailsPanel extends JPanel {
protected static SimpleAttributeSet titleAttrSet;
protected JLabel textLabel;
protected Font defaultFont;
protected JScrollPane sp;
/**
@ -105,15 +104,25 @@ public abstract class AbstractDetailsPanel extends JPanel {
*/
protected void createMainPanel() {
setLayout(new BorderLayout());
textLabel = new GDHtmlLabel("");
textLabel = new GDHtmlLabel() {
@Override
public Dimension getPreferredSize() {
// overridden to force word-wrapping by limiting the preferred size of the label
Dimension mySize = super.getPreferredSize();
int rightColumnWidth = AbstractDetailsPanel.this.getWidth() - LEFT_COLUMN_WIDTH;
mySize.width = Math.max(MIN_WIDTH, rightColumnWidth);
return mySize;
}
};
textLabel.setVerticalAlignment(SwingConstants.TOP);
textLabel.setOpaque(true);
textLabel.setBackground(Color.WHITE);
sp = new JScrollPane(textLabel);
sp.getVerticalScrollBar().setUnitIncrement(10);
sp.setPreferredSize(new Dimension(700, 200));
sp.setPreferredSize(new Dimension(MIN_WIDTH, 200));
add(sp, BorderLayout.CENTER);
defaultFont = new Font("Tahoma", Font.BOLD, 12);
}
/**
@ -126,7 +135,7 @@ public abstract class AbstractDetailsPanel extends JPanel {
protected void insertRowTitle(StringBuilder buffer, String rowName) {
buffer.append("<TR>");
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine(rowName + ":", titleAttrSet, buffer);
insertHTMLLine(buffer, rowName + ":", titleAttrSet);
buffer.append("</TD>");
}
@ -136,11 +145,12 @@ public abstract class AbstractDetailsPanel extends JPanel {
*
* @param buffer the string buffer to add to
* @param value the text to add
* @param attrSet the structure containing formatting information
* @param attributes the structure containing formatting information
*/
protected void insertRowValue(StringBuilder buffer, String value, SimpleAttributeSet attrSet) {
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine(value, attrSet, buffer);
protected void insertRowValue(StringBuilder buffer, String value,
SimpleAttributeSet attributes) {
buffer.append("<TD VALIGN=\"TOP\" WIDTH=\"80%\">");
insertHTMLLine(buffer, value, attributes);
buffer.append("</TD>");
buffer.append("</TR>");
}
@ -148,127 +158,54 @@ public abstract class AbstractDetailsPanel extends JPanel {
/**
* Adds text to a string buffer as an html-formatted string, adding formatting information
* as specified.
*
* @param string the string to add
* @param attributeSet the formatting instructions
* @param buffer the string buffer to add to
* @param string the string to add
* @param attributes the formatting instructions
*/
protected void insertHTMLString(String string, SimpleAttributeSet attributeSet,
StringBuilder buffer) {
protected void insertHTMLString(StringBuilder buffer, String string,
SimpleAttributeSet attributes) {
if (string == null) {
return;
}
buffer.append("<FONT COLOR=\"#");
Color foregroundColor = (Color) attributeSet.getAttribute(StyleConstants.Foreground);
buffer.append(createColorString(foregroundColor));
buffer.append("<FONT COLOR=\"");
Color foregroundColor = (Color) attributes.getAttribute(StyleConstants.Foreground);
buffer.append(HTMLUtilities.toHexString(foregroundColor));
buffer.append("\" FACE=\"");
buffer.append(attributeSet.getAttribute(StyleConstants.FontFamily).toString());
buffer.append(attributes.getAttribute(StyleConstants.FontFamily).toString());
buffer.append("\">");
Boolean isBold = (Boolean) attributeSet.getAttribute(StyleConstants.Bold);
Boolean isBold = (Boolean) attributes.getAttribute(StyleConstants.Bold);
isBold = (isBold == null) ? Boolean.FALSE : isBold;
String text = HTMLUtilities.escapeHTML(string);
if (isBold) {
buffer.append("<B>");
text = HTMLUtilities.bold(text);
}
buffer.append(HTMLUtilities.escapeHTML(string));
if (isBold) {
buffer.append("</B>");
}
buffer.append(text);
buffer.append("</FONT>");
}
/**
* Inserts a single line of html into a {@link StringBuffer}, with the given attributes.
*
* @param string the string to insert
* @param attributeSet the attributes to apply
* @param buffer the string buffer
* @param string the string to insert
* @param attributes the attributes to apply
*/
protected void insertHTMLLine(String string, SimpleAttributeSet attributeSet,
StringBuilder buffer) {
protected void insertHTMLLine(StringBuilder buffer, String string,
SimpleAttributeSet attributes) {
if (string == null) {
return;
}
insertHTMLString(string, attributeSet, buffer);
insertHTMLString(buffer, string, attributes);
// row padding - newline space
buffer.append("<BR>");
}
/**
* Returns a stringified version of the {@link Color} provided; eg: "8c0000"
*
* @param color the color to parse
* @return string version of the color
*/
protected String createColorString(Color color) {
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
return StringUtils.leftPad(Integer.toHexString(red), 2, "0") +
StringUtils.leftPad(Integer.toHexString(green), 2, "0") +
StringUtils.leftPad(Integer.toHexString(blue), 2, "0");
}
/**
* Returns a string with line breaks at the boundary of the window it's being displayed in.
* Without this the description would just run on in one long line.
*
* @param descr the string to format
* @return the formatted string
*/
protected String formatDescription(String descr) {
if (descr == null) {
return "";
}
int maxWidth = getMaxStringWidth();
int remainingWidth = maxWidth;
FontMetrics fm = textLabel.getFontMetrics(defaultFont);
int spaceSize = fm.charWidth(' ');
StringBuffer sb = new StringBuffer();
StringTokenizer st = new StringTokenizer(descr, " ");
while (st.hasMoreTokens()) {
String str = st.nextToken();
if (str.endsWith(".")) {
str = str + " ";
}
int strWidth = fm.stringWidth(str);
if (strWidth + spaceSize <= remainingWidth) {
sb.append(" ");
sb.append(str);
remainingWidth -= strWidth + spaceSize;
}
else {
sb.append("<BR>");
sb.append(str + " ");
remainingWidth = maxWidth - strWidth;
}
}
return sb.toString();
}
/**
* Returns the maximum size that one line of text can be when formatting the description.
*
* @return the number of characters in the string
*/
protected int getMaxStringWidth() {
int width = textLabel.getWidth();
if (width == 0) {
width = 700;
}
width -= LEFT_COLUMN_WIDTH + RIGHT_MARGIN; // allow for tabs and right margin
return width;
buffer.append(BR);
}
}

View file

@ -31,12 +31,12 @@ import docking.widgets.table.threaded.ThreadedTableModelListener;
class ExtensionDetailsPanel extends AbstractDetailsPanel {
/** Attribute sets define the visual characteristics for each field */
private static SimpleAttributeSet nameAttrSet;
private static SimpleAttributeSet descrAttrSet;
private static SimpleAttributeSet authorAttrSet;
private static SimpleAttributeSet createdOnAttrSet;
private static SimpleAttributeSet versionAttrSet;
private static SimpleAttributeSet pathAttrSet;
private SimpleAttributeSet nameAttrSet;
private SimpleAttributeSet descrAttrSet;
private SimpleAttributeSet authorAttrSet;
private SimpleAttributeSet createdOnAttrSet;
private SimpleAttributeSet versionAttrSet;
private SimpleAttributeSet pathAttrSet;
ExtensionDetailsPanel(ExtensionTablePanel tablePanel) {
super();
@ -44,7 +44,7 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
createMainPanel();
// Any time the table is reloaded or a new selection is made, we want to reload this
// panel. This ensures we are alwasy viewing data for the currently-selected item.
// panel. This ensures we are always viewing data for the currently-selected item.
tablePanel.getTableModel().addThreadedTableModelListener(new ThreadedTableModelListener() {
@Override
@ -87,7 +87,7 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
insertRowValue(buffer, details.getName(), nameAttrSet);
insertRowTitle(buffer, "Description");
insertRowValue(buffer, formatDescription(details.getDescription()), descrAttrSet);
insertRowValue(buffer, details.getDescription(), descrAttrSet);
insertRowTitle(buffer, "Author");
insertRowValue(buffer, details.getAuthor(), authorAttrSet);

View file

@ -35,15 +35,15 @@ import ghidra.framework.plugintool.util.PluginStatus;
*/
class PluginDetailsPanel extends AbstractDetailsPanel {
private static SimpleAttributeSet nameAttrSet;
private static SimpleAttributeSet depNameAttrSet;
private static SimpleAttributeSet descrAttrSet;
private static SimpleAttributeSet categoriesAttrSet;
private static SimpleAttributeSet classAttrSet;
private static SimpleAttributeSet locAttrSet;
private static SimpleAttributeSet developerAttrSet;
private static SimpleAttributeSet dependencyAttrSet;
private static SimpleAttributeSet noValueAttrSet;
private SimpleAttributeSet nameAttrSet;
private SimpleAttributeSet depNameAttrSet;
private SimpleAttributeSet descrAttrSet;
private SimpleAttributeSet categoriesAttrSet;
private SimpleAttributeSet classAttrSet;
private SimpleAttributeSet locAttrSet;
private SimpleAttributeSet developerAttrSet;
private SimpleAttributeSet dependencyAttrSet;
private SimpleAttributeSet noValueAttrSet;
private final PluginConfigurationModel model;
@ -54,14 +54,14 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
createMainPanel();
}
void setPluginDescription(PluginDescription pluginDescription) {
void setPluginDescription(PluginDescription descriptor) {
textLabel.setText("");
if (pluginDescription == null) {
if (descriptor == null) {
return;
}
List<PluginDescription> dependencies = model.getDependencies(pluginDescription);
List<PluginDescription> dependencies = model.getDependencies(descriptor);
Collections.sort(dependencies, (pd1, pd2) -> pd1.getName().compareTo(pd2.getName()));
StringBuilder buffer = new StringBuilder("<HTML>");
@ -69,45 +69,44 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
buffer.append("<TABLE cellpadding=2>");
insertRowTitle(buffer, "Name");
insertRowValue(buffer, pluginDescription.getName(),
insertRowValue(buffer, descriptor.getName(),
!dependencies.isEmpty() ? depNameAttrSet : nameAttrSet);
insertRowTitle(buffer, "Description");
insertRowValue(buffer, formatDescription(pluginDescription.getDescription()), descrAttrSet);
insertRowValue(buffer, descriptor.getDescription(), descrAttrSet);
insertRowTitle(buffer, "Status");
insertRowValue(buffer, pluginDescription.getStatus().getDescription(),
(pluginDescription.getStatus() == PluginStatus.RELEASED) ? titleAttrSet
: developerAttrSet);
insertRowValue(buffer, descriptor.getStatus().getDescription(),
(descriptor.getStatus() == PluginStatus.RELEASED) ? titleAttrSet : developerAttrSet);
insertRowTitle(buffer, "Package");
insertRowValue(buffer, pluginDescription.getPluginPackage().getName(), categoriesAttrSet);
insertRowValue(buffer, descriptor.getPluginPackage().getName(), categoriesAttrSet);
insertRowTitle(buffer, "Category");
insertRowValue(buffer, pluginDescription.getCategory(), categoriesAttrSet);
insertRowValue(buffer, descriptor.getCategory(), categoriesAttrSet);
insertRowTitle(buffer, "Plugin Class");
insertRowValue(buffer, pluginDescription.getPluginClass().getName(), classAttrSet);
insertRowValue(buffer, descriptor.getPluginClass().getName(), classAttrSet);
insertRowTitle(buffer, "Class Location");
insertRowValue(buffer, pluginDescription.getSourceLocation(), locAttrSet);
insertRowValue(buffer, descriptor.getSourceLocation(), locAttrSet);
insertRowTitle(buffer, "Used By");
buffer.append("<TD VALIGN=\"TOP\">");
if (dependencies.isEmpty()) {
insertHTMLLine("None", titleAttrSet, buffer);
insertHTMLLine(buffer, "None", noValueAttrSet);
}
else {
for (int i = 0; i < dependencies.size(); i++) {
insertHTMLString(dependencies.get(i).getPluginClass().getName(), dependencyAttrSet,
buffer);
insertHTMLString(buffer, dependencies.get(i).getPluginClass().getName(),
dependencyAttrSet);
if (i < dependencies.size() - 1) {
insertHTMLString("<BR>", dependencyAttrSet, buffer);
insertHTMLString(buffer, "<BR>", dependencyAttrSet);
}
}
insertHTMLLine("", titleAttrSet, buffer); // add a newline
insertHTMLLine(buffer, "", titleAttrSet); // add a newline
}
buffer.append("</TD>");
buffer.append("</TR>");
@ -116,18 +115,18 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
buffer.append("<TD VALIGN=\"TOP\">");
List<Class<?>> servicesRequired = pluginDescription.getServicesRequired();
List<Class<?>> servicesRequired = descriptor.getServicesRequired();
if (servicesRequired.isEmpty()) {
insertHTMLLine("None", titleAttrSet, buffer);
insertHTMLLine(buffer, "None", noValueAttrSet);
}
else {
for (int i = 0; i < servicesRequired.size(); i++) {
insertHTMLString(servicesRequired.get(i).getName(), dependencyAttrSet, buffer);
insertHTMLString(buffer, servicesRequired.get(i).getName(), dependencyAttrSet);
if (i < dependencies.size() - 1) {
insertHTMLString("<BR>", dependencyAttrSet, buffer);
insertHTMLString(buffer, "<BR>", dependencyAttrSet);
}
}
insertHTMLLine("", titleAttrSet, buffer); // add a newline
insertHTMLLine(buffer, "", titleAttrSet); // add a newline
}
buffer.append("</TD>");
buffer.append("</TR>");
@ -138,7 +137,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
//
// Optional: Actions loaded by this plugin
//
addLoadedActionsContent(buffer, pluginDescription);
addLoadedActionsContent(buffer, descriptor);
buffer.append("</TABLE>");
@ -155,13 +154,13 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
buffer.append("<TR>");
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine("Loaded Actions:", titleAttrSet, buffer);
insertHTMLLine(buffer, "Loaded Actions:", titleAttrSet);
buffer.append("</TD>");
Set<DockingActionIf> actions = model.getActionsForPlugin(pluginDescription);
if (actions.size() == 0) {
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine("No actions for plugin", noValueAttrSet, buffer);
insertHTMLLine(buffer, "No actions for plugin", noValueAttrSet);
buffer.append("</TD>");
buffer.append("</TR>");
return;
@ -174,7 +173,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
for (DockingActionIf dockableAction : actions) {
buffer.append("<TR><TD WIDTH=\"200\">");
insertHTMLString(dockableAction.getName(), locAttrSet, buffer);
insertHTMLString(buffer, dockableAction.getName(), locAttrSet);
buffer.append("</TD>");
buffer.append("<TD WIDTH=\"300\">");
@ -182,17 +181,17 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
String[] menuPath = menuBarData == null ? null : menuBarData.getMenuPath();
String menuPathString = createStringForMenuPath(menuPath);
if (menuPathString != null) {
insertHTMLString(menuPathString, locAttrSet, buffer);
insertHTMLString(buffer, menuPathString, locAttrSet);
}
else {
MenuData popupMenuData = dockableAction.getPopupMenuData();
String[] popupPath = popupMenuData == null ? null : popupMenuData.getMenuPath();
if (popupPath != null) {
insertHTMLString("(in a context popup menu)", noValueAttrSet, buffer);
insertHTMLString(buffer, "(in a context popup menu)", noValueAttrSet);
}
else {
insertHTMLString("Not in a menu", noValueAttrSet, buffer);
insertHTMLString(buffer, "Not in a menu", noValueAttrSet);
}
}
@ -202,10 +201,10 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
KeyStroke keyBinding = dockableAction.getKeyBinding();
if (keyBinding != null) {
String keyStrokeString = KeyBindingUtils.parseKeyStroke(keyBinding);
insertHTMLString(keyStrokeString, locAttrSet, buffer);
insertHTMLString(buffer, keyStrokeString, locAttrSet);
}
else {
insertHTMLString("No keybinding", noValueAttrSet, buffer);
insertHTMLString(buffer, "No keybinding", noValueAttrSet);
}
buffer.append("</TD></TR>");

View file

@ -182,8 +182,12 @@ public class PluginInstallerDialog extends DialogComponentProvider {
help.registerHelp(table, new HelpLocation(GenericHelpTopics.TOOL, "PluginDialog"));
table.getSelectionModel().addListSelectionListener(e -> {
int row = table.getSelectedRow();
if (e.getValueIsAdjusting()) {
return;
}
int row = table.getSelectedRow();
if (row < 0 || row > pluginDescriptions.size()) {
pluginDetailsPanel.setPluginDescription(null);
return;