Merge remote-tracking branch 'origin/GP-2844-dragonmacher-error-dialog-html--SQUASHED'

This commit is contained in:
Ryan Kurtz
2023-01-27 15:09:19 -05:00
5 changed files with 415 additions and 100 deletions

View File

@@ -21,7 +21,7 @@ import javax.swing.tree.TreePath;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.label.GHtmlLabel;
import docking.widgets.label.GLabel;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
@@ -112,7 +112,7 @@ public class ReplaceDataTypeAction extends DockingAction {
updatedPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 0));
updatedPanel.setLayout(new VerticalLayout(5));
GHtmlLabel label = new GHtmlLabel("<html>Choose the relacment data type: ");
GLabel label = new GLabel("Choose the relacement data type: ");
label.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
updatedPanel.add(label);

View File

@@ -111,12 +111,11 @@ class SymbolServerPanel extends JPanel {
JPanel buttonPanel = buildButtonPanel();
JScrollPane tableScrollPane = buildTable();
defaultConfigNotice = new JPanel();
defaultConfigNotice
.add(new GHtmlLabel(
"<html><center><font color=\"" + Colors.ERROR.toHexString() + "\"><br>" +
"Missing / invalid configuration.<br><br>" +
"Using default search location:<br>" + "Program's Import Location<br>",
SwingConstants.CENTER));
GHtmlLabel label = new GHtmlLabel("<html><center><font color=\"" +
Colors.ERROR.toHexString() + "\"><br>Missing / invalid configuration.<br><br>" +
"Using default search location:<br>Program's Import Location<br>");
label.setHorizontalAlignment(SwingConstants.CENTER);
defaultConfigNotice.add(label);
defaultConfigNotice.setPreferredSize(tableScrollPane.getPreferredSize());
additionalSearchLocationsPanel = new JPanel();

View File

@@ -0,0 +1,385 @@
/* ###
* 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 docking.widgets.label;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.StringReader;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.text.html.HTMLDocument.HTMLReader;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
import docking.widgets.GComponent;
import ghidra.util.Msg;
import ghidra.util.WebColors;
import ghidra.util.exception.AssertException;
/**
* Base class for labels that render html using a custom rendering kit.
* <p>
* This implementation uses custom html rendering. This custom rendering allows for basic
* formatting while eliminating potentially unsafe html tags. If for some reason this custom
* rendering is deficient, clients can instead use a standard Java {@link JLabel}.
* <p>
* Clients do not need to prefix label text with "&lt;html&gt;", as is required for a standard
* JLabel.
*/
public abstract class AbstractHtmlLabel extends JLabel
implements GComponent, PropertyChangeListener {
private static final String HTML_TAG = "<html>";
private boolean isUpdating = false;
protected AbstractHtmlLabel() {
addPropertyChangeListener(this);
setHTMLRenderingEnabled(false); // disable parent html rendering so we can do our own
}
protected AbstractHtmlLabel(String text) {
super(text);
addPropertyChangeListener(this);
setHTMLRenderingEnabled(false); // disable parent html rendering so we can do our own
}
@Override
public void setText(String text) {
// do not pass <html> up to the parent so that it does not install its own html rendering
if (text.toLowerCase().startsWith(HTML_TAG)) {
text = text.substring(HTML_TAG.length());
}
super.setText(text);
updateHtmlView();
}
@Override
public void updateUI() {
super.updateUI();
updateHtmlView();
}
private void updateHtmlView() {
// We need to add the html text back for our html editor kit to render html. Install our
// own View by the BasicHTML.propertyKey key so that the paint method uses it.
View customHtmlView = createHTMLView(HTML_TAG + getText());
putClientProperty(BasicHTML.propertyKey, customHtmlView);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (isUpdating) {
return;
}
String name = evt.getPropertyName();
if (!BasicHTML.propertyKey.equals(name)) {
return;
}
try {
isUpdating = true;
updateHtmlView();
}
finally {
isUpdating = false;
}
}
private View createHTMLView(String html) {
if (getFont() == null) {
return null; // initializing
}
GHtmlLabelEditorKit editorKit = new GHtmlLabelEditorKit();
Document document = editorKit.createDefaultDocument(getFont(), getForeground());
if (document == null) {
return null;
}
StringReader reader = new StringReader(html);
try {
editorKit.read(reader, document, 0);
}
catch (Throwable e) {
Msg.debug(this, "Error loading default html styles", e);
return null;
}
ViewFactory factory = editorKit.getViewFactory();
View view = factory.create(document.getDefaultRootElement());
return new ViewWrapper(view, factory);
}
private class GHtmlLabelEditorKit extends HTMLEditorKit {
@Override
public Document createDefaultDocument() {
StyleSheet baseStyleSheet = getStyleSheet();
StyleSheet styleSheet = new StyleSheet();
styleSheet.addStyleSheet(baseStyleSheet);
HTMLDocument document = new GHtmlLabelDocument(styleSheet, getFont(), getBackground());
document.setParser(getParser());
document.setAsynchronousLoadPriority(4);
document.setTokenThreshold(100);
return document;
}
public Document createDefaultDocument(Font defaultFont, Color foreground) {
StyleSheet defaultStyleSheet = new StyleSheet();
String defaultCss = """
p {
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
}
body {
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
}
""";
StringReader reader = new StringReader(defaultCss);
try {
defaultStyleSheet.loadRules(reader, null);
}
catch (Throwable e) {
Msg.debug(this, "Error loading default html styles", e);
return null;
}
defaultStyleSheet.addStyleSheet(super.getStyleSheet());
StyleSheet styleSheet = new StyleSheet();
styleSheet.addStyleSheet(defaultStyleSheet);
GHtmlLabelDocument document =
new GHtmlLabelDocument(styleSheet, defaultFont, foreground);
document.setAsynchronousLoadPriority(Integer.MAX_VALUE);
return document;
}
}
private class GHtmlLabelDocument extends HTMLDocument {
public GHtmlLabelDocument(StyleSheet ss, Font font, Color bg) {
super(ss);
setPreservesUnknownTags(false);
// loosely the default values used by HTMLDocument
String s = """
body {
font-family: %s;
font-size: %spt;
font-weight: %s;
font-style: %s;
color: %s;
}
""";
String family = font.getFamily();
String size = Integer.toString(font.getSize());
String weight = font.isBold() ? "700" : "400";
String style = font.isItalic() ? "italic" : "normal";
String color = WebColors.toString(bg);
String css = String.format(s, family, size, weight, style, color);
StyleSheet styleSheet = getStyleSheet();
styleSheet.addRule(css);
}
@Override
public ParserCallback getReader(int pos) {
return new GHtmlLabelReader(this, pos);
}
}
private class GHtmlLabelReader extends HTMLReader {
public GHtmlLabelReader(HTMLDocument htmlDocument, int offset) {
htmlDocument.super(offset);
//
// Remove support for any tags we do not need
// (see HTMLDocument.HTMLReader for default list of tags and actions)
//
// Supported Tags:
// A, B, BLOCKQUOTE, BODY, BR, CENTER, CODE, DIV, FONT, H1, H2, H3, H4, H5, H6, HR,
// HTML, I, LI, NOBR, OL, P, PRE, SPAN, STRIKE, SUB, SUP, TABLE, TD, TEXTAREA, TH, TR,
// TT, U, UL
//
// (Note: this list was made in a cursory fashion, keeping basic formatting tags while
// removing any that allow for remote accesses, such as the IMG tag.)
//
TagAction stub = new TagAction();
HTML.Tag[] toRemove = { HTML.Tag.ADDRESS, HTML.Tag.APPLET, HTML.Tag.AREA, HTML.Tag.BASE,
HTML.Tag.BASEFONT, HTML.Tag.BIG, HTML.Tag.CAPTION, HTML.Tag.CITE, HTML.Tag.DD,
HTML.Tag.DFN, HTML.Tag.DIR, HTML.Tag.DL, HTML.Tag.DT, HTML.Tag.EM, HTML.Tag.FORM,
HTML.Tag.FRAME, HTML.Tag.FRAMESET, HTML.Tag.HEAD, HTML.Tag.IMG, HTML.Tag.INPUT,
HTML.Tag.ISINDEX, HTML.Tag.KBD, HTML.Tag.LINK, HTML.Tag.MAP, HTML.Tag.MENU,
HTML.Tag.META, HTML.Tag.NOFRAMES, HTML.Tag.OBJECT, HTML.Tag.OPTION, HTML.Tag.PARAM,
HTML.Tag.SAMP, HTML.Tag.SCRIPT, HTML.Tag.SELECT, HTML.Tag.SMALL, HTML.Tag.S,
HTML.Tag.STRONG, HTML.Tag.STYLE, HTML.Tag.TITLE, HTML.Tag.VAR };
replace(toRemove, stub);
}
private void replace(HTML.Tag[] tags, TagAction action) {
for (HTML.Tag tag : tags) {
registerTag(tag, action);
}
}
}
private class ViewWrapper extends View {
private int width;
private View htmlView;
private ViewFactory factory;
private JComponent container;
ViewWrapper(View view, ViewFactory viewFactory) {
super(null);
this.container = AbstractHtmlLabel.this;
this.htmlView = view;
this.factory = viewFactory;
htmlView.setParent(this);
setSize(htmlView.getPreferredSpan(X_AXIS), htmlView.getPreferredSpan(Y_AXIS));
}
@Override
public float getPreferredSpan(int axis) {
if (axis == X_AXIS) {
return width; // have the label use its assigned width
}
return htmlView.getPreferredSpan(axis);
}
@Override
public float getMinimumSpan(int axis) {
return htmlView.getMinimumSpan(axis); // use delegate's minimum
}
@Override
public float getMaximumSpan(int axis) {
return Integer.MAX_VALUE; // no restrictions
}
@Override
public void preferenceChanged(View child, boolean w, boolean h) {
container.revalidate();
container.repaint();
}
@Override
public void paint(Graphics g, Shape allocation) {
Rectangle alloc = allocation.getBounds();
htmlView.setSize(alloc.width, alloc.height);
htmlView.paint(g, allocation);
}
@Override
public void setSize(float w, float h) {
// have our label always use the available width
this.width = (int) w;
htmlView.setSize(w, h);
}
@Override
public void setParent(View parent) {
throw new AssertException("setParent() unexpectedly called"); // shouldn't happen
}
@Override
public AttributeSet getAttributes() {
return null; // no special attributes to use outside of the document
}
@Override
public Container getContainer() {
return container;
}
@Override
public ViewFactory getViewFactory() {
return factory;
}
@Override
public int getViewCount() {
return 1;
}
@Override
public View getView(int n) {
return htmlView;
}
@Override
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
return htmlView.modelToView(pos, a, b);
}
@Override
public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a)
throws BadLocationException {
return htmlView.modelToView(p0, b0, p1, b1, a);
}
@Override
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
return htmlView.viewToModel(x, y, a, bias);
}
@Override
public Document getDocument() {
return htmlView.getDocument();
}
@Override
public int getStartOffset() {
return htmlView.getStartOffset();
}
@Override
public int getEndOffset() {
return htmlView.getEndOffset();
}
@Override
public Element getElement() {
return htmlView.getElement();
}
@Override
public float getAlignment(int axis) {
return htmlView.getAlignment(axis);
}
}
}

View File

@@ -15,15 +15,19 @@
*/
package docking.widgets.label;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import docking.widgets.GComponent;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.checkbox.GHtmlCheckBox;
/**
* A 'dynamic' label (the text can be changed), with HTML rendering allowed.
* <p>
* Clients do not need to prefix label text with "&lt;html&gt;", as is required for a standard
* JLabel.
* <p>
*
* See also:
* <table border=1><caption></caption>
* <tr><th>Class</th><th>Mutable text</th><th>HTML rendering</th><th>Description</th></tr>
@@ -37,7 +41,7 @@ import docking.widgets.checkbox.GHtmlCheckBox;
* <tr><td>{@link GHtmlCheckBox}</td><td></td><td>YES</td><td>Html checkbox</td></tr>
* </table>
*/
public class GDHtmlLabel extends JLabel implements GComponent {
public class GDHtmlLabel extends AbstractHtmlLabel {
/**
* Creates a label with no image and no text, with {@link SwingConstants#LEADING} horizontal
@@ -47,7 +51,6 @@ public class GDHtmlLabel extends JLabel implements GComponent {
* <p>
*/
public GDHtmlLabel() {
super();
}
/**
@@ -61,43 +64,4 @@ public class GDHtmlLabel extends JLabel implements GComponent {
public GDHtmlLabel(String text) {
super(text);
}
/**
* Creates a label with the specified text and horizontal alignment,
* with HTML rendering allowed.
* <p>
* See {@link JLabel#JLabel(String, int)}.
* <p>
* @param text string to be displayed by the label
* @param horizontalAlignment One of
* {@link SwingConstants#LEFT},
* {@link SwingConstants#CENTER},
* {@link SwingConstants#RIGHT},
* {@link SwingConstants#LEADING},
* {@link SwingConstants#TRAILING}
*/
public GDHtmlLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
}
/**
* Creates a label with the specified text, image and horizontal alignment,
* with HTML rendering allowed.
* <p>
* See {@link JLabel#JLabel(String, Icon, int)}.
* <p>
*
* @param text string to be displayed by the label
* @param icon image to be displayed by the label
* @param horizontalAlignment One of
* {@link SwingConstants#LEFT},
* {@link SwingConstants#CENTER},
* {@link SwingConstants#RIGHT},
* {@link SwingConstants#LEADING},
* {@link SwingConstants#TRAILING}
*/
public GDHtmlLabel(String text, Icon icon, int horizontalAlignment) {
super(text, icon, horizontalAlignment);
}
}

View File

@@ -15,11 +15,11 @@
*/
package docking.widgets.label;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.GComponent;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.checkbox.GHtmlCheckBox;
import ghidra.util.Msg;
@@ -28,6 +28,10 @@ import utilities.util.reflection.ReflectionUtilities;
/**
* An immutable label (the text can NOT be changed), with HTML rendering allowed.
* <p>
* Clients do not need to prefix label text with "&lt;html&gt;", as is required for a standard
* JLabel.
*
* <p>
* See also:
* <table border=1><caption></caption>
* <tr><th>Class</th><th>Mutable text</th><th>HTML rendering</th><th>Description</th></tr>
@@ -41,22 +45,21 @@ import utilities.util.reflection.ReflectionUtilities;
* <tr><td>{@link GHtmlCheckBox}</td><td></td><td>YES</td><td>Html checkbox</td></tr>
* </table>
*/
public class GHtmlLabel extends JLabel implements GComponent {
public class GHtmlLabel extends AbstractHtmlLabel {
/**
* Creates a immutable label with no image and no text, with {@link SwingConstants#LEADING} horizontal
* alignment, with HTML rendering allowed.
* Creates a immutable label with no image and no text, with {@link SwingConstants#LEADING}
* horizontal alignment, with HTML rendering allowed.
* <p>
* See {@link JLabel#JLabel()}.
* <p>
*/
public GHtmlLabel() {
super();
}
/**
* Creates a immutable label with the specified text, with {@link SwingConstants#LEADING} horizontal
* alignment, with HTML rendering allowed.
* Creates a immutable label with the specified text, with {@link SwingConstants#LEADING}
* horizontal alignment, with HTML rendering allowed.
* <p>
* See {@link JLabel#JLabel(String)}.
* <p>
@@ -66,53 +69,16 @@ public class GHtmlLabel extends JLabel implements GComponent {
super(text);
}
/**
* Creates a immutable label with the specified text and horizontal alignment,
* with HTML rendering allowed.
* <p>
* See {@link JLabel#JLabel(String, int)}.
* <p>
* @param text string to be displayed by the label
* @param horizontalAlignment One of
* {@link SwingConstants#LEFT},
* {@link SwingConstants#CENTER},
* {@link SwingConstants#RIGHT},
* {@link SwingConstants#LEADING},
* {@link SwingConstants#TRAILING}
*/
public GHtmlLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
}
/**
* Creates a immutable label with the specified text, image and horizontal alignment,
* with HTML rendering allowed.
* <p>
* See {@link JLabel#JLabel(String, Icon, int)}.
* <p>
*
* @param text string to be displayed by the label
* @param icon image to be displayed by the label
* @param horizontalAlignment One of
* {@link SwingConstants#LEFT},
* {@link SwingConstants#CENTER},
* {@link SwingConstants#RIGHT},
* {@link SwingConstants#LEADING},
* {@link SwingConstants#TRAILING}
*/
public GHtmlLabel(String text, Icon icon, int horizontalAlignment) {
super(text, icon, horizontalAlignment);
}
/**
* This is a half-way method of turning this label into an immutable instance.
* <p>
* If the user has a type of "GHtmlLabel", they will see the deprecated warning on calls to setText().
* If the user has a type of "GHtmlLabel", they will see the deprecated warning on calls to
* setText().
* <p>
* If there are calls to setText() after the initial value has been set by the ctor, a
* If there are calls to setText() after the initial value has been set by the constructor, a
* warning will be printed in the log.
* <p>
* @param text string this label will display
* @param text string this label will display
*/
@Deprecated
@Override
@@ -126,4 +92,5 @@ public class GHtmlLabel extends JLabel implements GComponent {
}
super.setText(text);
}
}