Merge remote-tracking branch 'origin/GP-65-dragonmacher-error-dialog'

This commit is contained in:
ghidravore 2020-09-02 14:37:55 -04:00
commit fc994cda95
44 changed files with 1784 additions and 1662 deletions

View file

@ -0,0 +1,58 @@
/* ###
* 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;
import utility.function.Callback;
/**
* A dialog that is meant to be extended for showing exceptions
*/
public abstract class AbstractErrDialog extends DialogComponentProvider {
// at some point, there are too many exceptions to show
protected static final int MAX_EXCEPTIONS = 100;
private static final String ERRORS_PREFIX = " (";
private static final String ERRORS_SUFFIX = " Errors)";
private Callback closedCallback = Callback.dummy();
protected AbstractErrDialog(String title) {
super(title, true, false, true, false);
}
@Override
protected final void dialogClosed() {
closedCallback.call();
}
/**
* Returns the string message of this error dialog
* @return the message
*/
public abstract String getMessage();
abstract void addException(String message, Throwable t);
abstract int getExceptionCount();
void updateTitle() {
setTitle(getTitle() + ERRORS_PREFIX + getExceptionCount() + ERRORS_SUFFIX);
}
void setClosedCallback(Callback callback) {
closedCallback = Callback.dummyIfNull(callback);
}
}

View file

@ -220,6 +220,12 @@ public class DockingDialog extends JDialog implements HelpDescriptor {
component.escapeCallback();
}
@Override
public void windowClosed(WindowEvent e) {
// this call is needed to handle the case where the dialog is closed by Java and
// not by the user closing the dialog or calling close() through the API
cleanup();
}
};
this.addWindowListener(windowAdapter);
modalFixWindowAdapter = new WindowAdapter() {
@ -249,6 +255,10 @@ public class DockingDialog extends JDialog implements HelpDescriptor {
}
void close() {
cleanup();
}
private void cleanup() {
if (component.getRemberSize() || component.getRememberLocation()) {
String key = getKey();
Rectangle rect = getBounds();
@ -258,7 +268,10 @@ public class DockingDialog extends JDialog implements HelpDescriptor {
component.setDialog(null);
removeWindowListener(windowAdapter);
// this will do nothing if already closed
setVisible(false);
component.dialogClosed();
component = null;
getContentPane().removeAll();

View file

@ -17,7 +17,6 @@ package docking;
import java.awt.Component;
import java.awt.Window;
import java.io.*;
import docking.widgets.OkDialog;
import docking.widgets.OptionDialog;
@ -26,7 +25,16 @@ import ghidra.util.exception.MultipleCauses;
public class DockingErrorDisplay implements ErrorDisplay {
private static final int TRACE_BUFFER_SIZE = 250;
/**
* Error dialog used to append exceptions.
*
* <p>While this dialog is showing all new exceptions will be added to the dialog. When
* this dialog is closed, this reference will be cleared.
*
* <p>Note: all use of this variable <b>must be on the Swing thread</b> to avoid thread
* visibility issues.
*/
private static AbstractErrDialog activeDialog;
ConsoleErrorDisplay consoleDisplay = new ConsoleErrorDisplay();
@ -52,8 +60,8 @@ public class DockingErrorDisplay implements ErrorDisplay {
private void displayMessage(MessageType messageType, ErrorLogger errorLogger, Object originator,
Component parent, String title, Object message, Throwable throwable) {
int dialogType = OptionDialog.PLAIN_MESSAGE;
int dialogType = OptionDialog.PLAIN_MESSAGE;
String messageString = message != null ? message.toString() : null;
String rawMessage = HTMLUtilities.fromHTML(messageString);
switch (messageType) {
@ -75,7 +83,7 @@ public class DockingErrorDisplay implements ErrorDisplay {
break;
}
showDialog(title, message, throwable, dialogType, messageString, getWindow(parent));
showDialog(title, throwable, dialogType, messageString, getWindow(parent));
}
private Component getWindow(Component component) {
@ -85,33 +93,45 @@ public class DockingErrorDisplay implements ErrorDisplay {
return component;
}
private void showDialog(final String title, final Object message, final Throwable throwable,
private void showDialog(final String title, final Throwable throwable,
final int dialogType, final String messageString, final Component parent) {
SystemUtilities.runIfSwingOrPostSwingLater(
() -> doShowDialog(title, message, throwable, dialogType, messageString, parent));
}
private void doShowDialog(final String title, final Object message, final Throwable throwable,
int dialogType, String messageString, Component parent) {
DialogComponentProvider dialog = null;
if (throwable != null) {
dialog = createErrorDialog(title, message, throwable, messageString);
if (dialogType == OptionDialog.ERROR_MESSAGE) {
// Note: all calls to manipulate the error dialog must be on the Swing thread to
// guarantee thread visibility to our state variables
Swing.runIfSwingOrRunLater(
() -> showDialogOnSwing(title, throwable, dialogType, messageString, parent));
}
else {
dialog = new OkDialog(title, messageString, dialogType);
DockingWindowManager.showDialog(parent,
new OkDialog(title, messageString, dialogType));
}
DockingWindowManager.showDialog(parent, dialog);
}
private DialogComponentProvider createErrorDialog(final String title, final Object message,
final Throwable throwable, String messageString) {
private void showDialogOnSwing(String title, Throwable throwable,
int dialogType, String messageString, Component parent) {
if (activeDialog != null) {
activeDialog.addException(messageString, throwable);
return;
}
activeDialog = createErrorDialog(title, throwable, messageString);
activeDialog.setClosedCallback(() -> {
activeDialog.setClosedCallback(null);
activeDialog = null;
});
DockingWindowManager.showDialog(parent, activeDialog);
}
private AbstractErrDialog createErrorDialog(String title, Throwable throwable,
String messageString) {
if (containsMultipleCauses(throwable)) {
return new ErrLogExpandableDialog(title, messageString, throwable);
}
return ErrLogDialog.createExceptionDialog(title, messageString,
buildStackTrace(throwable, message == null ? throwable.getMessage() : messageString));
return ErrLogDialog.createExceptionDialog(title, messageString, throwable);
}
private boolean containsMultipleCauses(Throwable throwable) {
@ -125,34 +145,4 @@ public class DockingErrorDisplay implements ErrorDisplay {
return containsMultipleCauses(throwable.getCause());
}
/**
* Build a displayable stack trace from a Throwable
*
* @param t the throwable
* @param msg message prefix
* @return multi-line stack trace
*/
private String buildStackTrace(Throwable t, String msg) {
StringBuffer sb = new StringBuffer(TRACE_BUFFER_SIZE);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
if (msg != null) {
ps.println(msg);
}
t.printStackTrace(ps);
sb.append(baos.toString());
ps.close();
try {
baos.close();
}
catch (IOException e) {
// shouldn't happen--not really connected to the system
}
return sb.toString();
}
}

View file

@ -20,20 +20,35 @@ import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.*;
import docking.widgets.ScrollableTextArea;
import docking.widgets.label.GHtmlLabel;
import docking.widgets.label.GIconLabel;
import docking.widgets.table.*;
import generic.json.Json;
import generic.util.WindowUtilities;
import ghidra.docking.settings.Settings;
import ghidra.framework.Application;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.util.HTMLUtilities;
import ghidra.util.Swing;
import ghidra.util.table.column.DefaultTimestampRenderer;
import ghidra.util.table.column.GColumnRenderer;
import utilities.util.reflection.ReflectionUtilities;
public class ErrLogDialog extends DialogComponentProvider {
private static final int TEXT_ROWS = 30;
/**
* A dialog that takes error text and displays it with an option details button. If there is
* an {@link ErrorReporter}, then a button is provided to report the error.
*/
public class ErrLogDialog extends AbstractErrDialog {
private static final int TEXT_ROWS = 20;
private static final int TEXT_COLUMNS = 80;
private static final int ERROR_BUFFER_SIZE = 1024;
private static final String SEND = "Log Error...";
private static final String DETAIL = "Details >>>";
@ -46,32 +61,30 @@ public class ErrLogDialog extends DialogComponentProvider {
/** tracks 'details panel' open state across invocations */
private static boolean isShowingDetails = false;
private int errorId = 0;
// state-dependent gui members
private ErrorDetailsPanel detailsPanel;
private ErrorDetailsSplitPane detailsPane;
private JButton detailsButton;
private JButton sendButton;
private JPanel mainPanel;
private static ErrorReporter errorReporter;
public static ErrLogDialog createExceptionDialog(String title, String message, String details) {
return new ErrLogDialog(title, message, details, true);
private List<ErrorEntry> errors = new ArrayList<>();
public static ErrLogDialog createExceptionDialog(String title, String message, Throwable t) {
return new ErrLogDialog(title, message, t);
}
public static ErrLogDialog createLogMessageDialog(String title, String message,
String details) {
return new ErrLogDialog(title, message, details, false);
}
private ErrLogDialog(String title, String message, Throwable throwable) {
super(title != null ? title : "Error");
ErrorEntry error = new ErrorEntry(message, throwable);
errors.add(error);
/**
* Constructor.
* Used by the Err class's static methods for logging various
* kinds of errors: Runtime, System, User, Asserts
*/
private ErrLogDialog(String title, String message, String details, boolean isException) {
super(title != null ? title : "Error", true, false, true, false);
setRememberSize(false);
setRememberLocation(false);
buildMainPanel(message, addUsefulReportingInfo(details), isException);
buildMainPanel(message);
}
private String addUsefulReportingInfo(String details) {
@ -127,13 +140,21 @@ public class ErrLogDialog extends DialogComponentProvider {
return errorReporter;
}
private void buildMainPanel(String message, String details, boolean isException) {
private void buildMainPanel(String message) {
JPanel introPanel = new JPanel(new BorderLayout(10, 10));
introPanel.add(
new GIconLabel(UIManager.getIcon("OptionPane.errorIcon"), SwingConstants.RIGHT),
BorderLayout.WEST);
introPanel.add(new GHtmlLabel(HTMLUtilities.toHTML(message)), BorderLayout.CENTER);
introPanel.add(new GHtmlLabel(HTMLUtilities.toHTML(message)) {
@Override
public Dimension getPreferredSize() {
// rendering HTML the label can expand larger than the screen; keep it reasonable
Dimension size = super.getPreferredSize();
size.width = 300;
return size;
}
}, BorderLayout.CENTER);
mainPanel = new JPanel(new BorderLayout(10, 20));
mainPanel.add(introPanel, BorderLayout.NORTH);
@ -141,21 +162,15 @@ public class ErrLogDialog extends DialogComponentProvider {
sendButton = new JButton(SEND);
sendButton.addActionListener(e -> sendDetails());
detailsPanel = new ErrorDetailsPanel();
detailsButton = new JButton(isShowingDetails ? CLOSE : DETAIL);
detailsButton.addActionListener(e -> {
String label = detailsButton.getText();
showDetails(label.equals(DETAIL));
});
if (isException) {
detailsPanel.setExceptionMessage(details);
}
else {
detailsPanel.setLogMessage(details);
}
detailsPane = new ErrorDetailsSplitPane();
JPanel buttonPanel = new JPanel(new GridLayout(2, 1, 5, 5));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5));
buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
if (errorReporter != null) {
buttonPanel.add(sendButton);
@ -163,16 +178,16 @@ public class ErrLogDialog extends DialogComponentProvider {
buttonPanel.add(detailsButton);
introPanel.add(buttonPanel, BorderLayout.EAST);
mainPanel.add(detailsPanel, BorderLayout.CENTER);
mainPanel.add(detailsPane, BorderLayout.CENTER);
addWorkPanel(mainPanel);
addOKButton();
setDefaultButton(okButton);
// show the details panel if it was showing previously
detailsPanel.setVisible(isShowingDetails);
// setHelpLocation(new HelpLocation(HelpTopics.INTRO, "Err"));
detailsPane.setVisible(isShowingDetails);
detailsPane.selectFirstError();
}
@Override
@ -185,82 +200,69 @@ public class ErrLogDialog extends DialogComponentProvider {
cancelCallback();
}
/**
* Send error details from dialog.
*/
private void sendDetails() {
String details = detailsPanel.getDetails();
String details = detailsPane.getDetails();
String title = getTitle();
close();
errorReporter.report(rootPanel, title, details);
}
/**
* opens and closes the details panel; used also by Err when
* showLog is called from SessionGui Help menu to show details
* when visible
*/
private void showDetails(boolean visible) {
isShowingDetails = visible;
String label = (visible ? CLOSE : DETAIL);
detailsButton.setText(label);
detailsPanel.setVisible(visible);
detailsPane.setVisible(visible);
repack(); // need to re-pack so the detailsPanel can be hidden correctly
}
// custom "pack" so the detailsPanel can be shown/hidden correctly
@Override
protected void repack() {
// hide the dialog so that the user doesn't see us resize and then move it, which looks
// awkward
getDialog().setVisible(false);
detailsPanel.invalidate(); // force to be invalid so resizes correctly
rootPanel.validate();
super.repack();
// center the dialog after its size changes for a cleaner appearance
DockingDialog dialog = getDialog();
Container parent = dialog.getParent();
Point centerPoint = WindowUtilities.centerOnComponent(parent, dialog);
dialog.setLocation(centerPoint);
getDialog().setVisible(true);
public String getMessage() {
return detailsPane.getMessage();
}
@Override
protected void dialogShown() {
// TODO test that the parent DockingDialog code handles this....
WindowUtilities.ensureOnScreen(getDialog());
Swing.runLater(() -> okButton.requestFocusInWindow());
}
/**
* scrolled text panel used to display the error message details;
* each time an error message is "added", appends the contents to
* the internal StringBuffer.
*/
private class ErrorDetailsPanel extends JPanel {
private ScrollableTextArea textDetails;
private StringBuffer errorDetailsBuffer;
private Dimension closedSize;
@Override
void addException(String message, Throwable t) {
int n = errors.size();
if (n > MAX_EXCEPTIONS) {
return;
}
errors.add(new ErrorEntry(message, t));
detailsPane.update();
updateTitle(); // signal the new error
}
@Override
int getExceptionCount() {
return errors.size();
}
private class ErrorDetailsSplitPane extends JSplitPane {
private final double TOP_PREFERRED_RESIZE_WEIGHT = .80;
private ErrorDetailsPanel detailsPanel;
private ErrorDetailsTablePanel tablePanel;
private Dimension openedSize;
private ErrorDetailsPanel() {
super(new BorderLayout(0, 0));
errorDetailsBuffer = new StringBuffer(ERROR_BUFFER_SIZE);
textDetails = new ScrollableTextArea(TEXT_ROWS, TEXT_COLUMNS);
textDetails.setEditable(false);
add(textDetails, BorderLayout.CENTER);
validate();
textDetails.scrollToBottom();
ErrorDetailsSplitPane() {
super(VERTICAL_SPLIT);
setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT);
// set the initial preferred size of this panel
// when "closed"
Rectangle bounds = getBounds();
closedSize = new Dimension(bounds.width, 0);
detailsPanel = new ErrorDetailsPanel();
tablePanel = new ErrorDetailsTablePanel();
setTopComponent(detailsPanel);
setBottomComponent(tablePanel);
addComponentListener(new ComponentAdapter() {
@Override
@ -269,51 +271,283 @@ public class ErrLogDialog extends DialogComponentProvider {
return;
}
Rectangle localBounds = getBounds();
if (detailsButton.getText().equals(DETAIL)) {
closedSize.width = localBounds.width;
}
else {
if (!detailsButton.getText().equals(DETAIL)) {
openedSize = new Dimension(localBounds.width, localBounds.height);
}
}
});
}
void selectFirstError() {
tablePanel.selectFirstError();
}
String getDetails() {
return detailsPanel.getDetails();
}
String getMessage() {
return detailsPanel.getMessage();
}
void setError(ErrorEntry err) {
detailsPanel.setError(err);
}
void update() {
tablePanel.update();
}
@Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
if (detailsButton.getText().equals(DETAIL)) {
return closedSize;
return superSize;
}
if (openedSize == null) {
return super.getPreferredSize();
return superSize;
}
return openedSize;
}
}
/**
* resets the current error buffer to the contents of msg
*/
private void setLogMessage(String msg) {
errorDetailsBuffer = new StringBuffer(msg);
textDetails.setText(msg);
private class ErrorDetailsTablePanel extends JPanel {
// scroll to bottom so user is viewing the last message
private ErrEntryTableModel model;
private GTable errorsTable;
private GTableFilterPanel<ErrorEntry> tableFilterPanel;
ErrorDetailsTablePanel() {
setLayout(new BorderLayout());
model = new ErrEntryTableModel();
errorsTable = new GTable(model);
tableFilterPanel = new GTableFilterPanel<ErrorEntry>(errorsTable, model);
errorsTable.getSelectionManager().addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) {
return;
}
int firstIndex = errorsTable.getSelectedRow();
if (firstIndex == -1) {
return;
}
ErrorEntry err = tableFilterPanel.getRowObject(firstIndex);
detailsPane.setError(err);
});
JPanel tablePanel = new JPanel(new BorderLayout());
tablePanel.add(new JScrollPane(errorsTable), BorderLayout.CENTER);
tablePanel.add(tableFilterPanel, BorderLayout.SOUTH);
add(tablePanel, BorderLayout.CENTER);
// initialize this value to something small so the full dialog will not consume the
// entire screen height
setPreferredSize(new Dimension(400, 100));
}
void selectFirstError() {
errorsTable.selectRow(0);
}
void update() {
model.fireTableDataChanged();
}
}
/**
* scrolled text panel used to display the error message details;
* each time an error message is "added", appends the contents to
* the internal StringBuffer.
*/
private class ErrorDetailsPanel extends JPanel {
private ScrollableTextArea textDetails;
private ErrorEntry error;
private ErrorDetailsPanel() {
super(new BorderLayout(0, 0));
textDetails = new ScrollableTextArea(TEXT_ROWS, TEXT_COLUMNS);
textDetails.setEditable(false);
add(textDetails, BorderLayout.CENTER);
validate();
textDetails.scrollToBottom();
}
private void setExceptionMessage(String msg) {
errorDetailsBuffer = new StringBuffer(msg);
textDetails.setText(msg);
void setError(ErrorEntry e) {
error = e;
setExceptionMessage(e.getDetailsText());
}
private void setExceptionMessage(String message) {
String updated = addUsefulReportingInfo(message);
textDetails.setText(updated);
// scroll to the top the see the pertinent part of the exception
textDetails.scrollToTop();
}
private final String getDetails() {
return errorDetailsBuffer.toString();
String getDetails() {
return textDetails.getText();
}
String getMessage() {
return error.getMessage();
}
}
private class ErrorEntry {
private String message;
private String details;
private Date timestamp = new Date();
private int myId = ++errorId;
ErrorEntry(String message, Throwable t) {
String updated = message;
if (HTMLUtilities.isHTML(updated)) {
updated = HTMLUtilities.fromHTML(updated);
}
this.message = updated;
if (t != null) {
this.details = ReflectionUtilities.stackTraceToString(t);
}
}
int getId() {
return myId;
}
String getMessage() {
return message;
}
Date getTimestamp() {
return timestamp;
}
String getDetailsText() {
if (details == null) {
return message;
}
return details;
}
String getDetails() {
return details;
}
@Override
public String toString() {
return Json.toString(this);
}
}
private class ErrEntryTableModel extends GDynamicColumnTableModel<ErrorEntry, Object> {
public ErrEntryTableModel() {
super(new ServiceProviderStub());
}
@Override
protected TableColumnDescriptor<ErrorEntry> createTableColumnDescriptor() {
TableColumnDescriptor<ErrorEntry> descriptor = new TableColumnDescriptor<ErrorEntry>();
descriptor.addVisibleColumn(new IdColumn(), 1, true);
descriptor.addVisibleColumn(new MessageColumn());
descriptor.addHiddenColumn(new DetailsColumn());
descriptor.addVisibleColumn(new TimestampColumn());
return descriptor;
}
@Override
public String getName() {
return "Unexpectd Errors";
}
@Override
public List<ErrorEntry> getModelData() {
return errors;
}
@Override
public Object getDataSource() {
return null;
}
private class IdColumn extends AbstractDynamicTableColumnStub<ErrorEntry, Integer> {
@Override
public Integer getValue(ErrorEntry rowObject, Settings settings, ServiceProvider sp)
throws IllegalArgumentException {
return rowObject.getId();
}
@Override
public String getColumnName() {
return "#";
}
@Override
public int getColumnPreferredWidth() {
return 40;
}
}
private class MessageColumn extends AbstractDynamicTableColumnStub<ErrorEntry, String> {
@Override
public String getValue(ErrorEntry rowObject, Settings settings, ServiceProvider sp)
throws IllegalArgumentException {
return rowObject.getMessage();
}
@Override
public String getColumnName() {
return "Message";
}
}
private class DetailsColumn extends AbstractDynamicTableColumnStub<ErrorEntry, String> {
@Override
public String getValue(ErrorEntry rowObject, Settings settings, ServiceProvider sp)
throws IllegalArgumentException {
return rowObject.getDetails();
}
@Override
public String getColumnName() {
return "Details";
}
}
private class TimestampColumn extends AbstractDynamicTableColumnStub<ErrorEntry, Date> {
private GColumnRenderer<Date> renderer = new DefaultTimestampRenderer();
@Override
public Date getValue(ErrorEntry rowObject, Settings settings, ServiceProvider sp)
throws IllegalArgumentException {
return rowObject.getTimestamp();
}
@Override
public String getColumnName() {
return "Time";
}
@Override
public GColumnRenderer<Date> getColumnRenderer() {
return renderer;
}
}
}
}

View file

@ -32,12 +32,12 @@ import docking.widgets.label.GHtmlLabel;
import docking.widgets.tree.*;
import docking.widgets.tree.support.GTreeDragNDropHandler;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.exception.MultipleCauses;
import ghidra.util.html.HTMLElement;
import resources.ResourceManager;
import util.CollectionUtils;
public class ErrLogExpandableDialog extends DialogComponentProvider {
public class ErrLogExpandableDialog extends AbstractErrDialog {
public static ImageIcon IMG_REPORT = ResourceManager.loadImage("images/report.png");
public static ImageIcon IMG_EXCEPTION = ResourceManager.loadImage("images/exception.png");
public static ImageIcon IMG_FRAME_ELEMENT =
@ -53,113 +53,21 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
private static boolean showingDetails = false;
protected ReportRootNode root;
protected GTree excTree;
protected GTree tree;
private List<Throwable> errors = new ArrayList<>();
/** This spacer addresses the optical impression that the message panel changes size when showing details */
protected Component horizontalSpacer;
protected JButton detailButton;
protected JButton sendButton;
protected boolean hasConsole = false;
protected JPopupMenu popup;
protected static class ExcTreeTransferHandler extends TransferHandler
implements GTreeDragNDropHandler {
protected ErrLogExpandableDialog(String title, String msg, Throwable throwable) {
super(title);
protected ReportRootNode root;
errors.add(throwable);
public ExcTreeTransferHandler(ReportRootNode root) {
this.root = root;
}
@Override
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> transferNodes) {
return new DataFlavor[] { DataFlavor.stringFlavor };
}
@Override
protected Transferable createTransferable(JComponent c) {
ArrayList<GTreeNode> nodes = new ArrayList<>();
for (TreePath path : ((JTree) c).getSelectionPaths()) {
nodes.add((GTreeNode) path.getLastPathComponent());
}
try {
return new StringSelection(
(String) getTransferData(nodes, DataFlavor.stringFlavor));
}
catch (UnsupportedFlavorException e) {
Msg.debug(this, e.getMessage(), e);
}
return null;
}
@Override
public Object getTransferData(List<GTreeNode> transferNodes, DataFlavor flavor)
throws UnsupportedFlavorException {
if (flavor != DataFlavor.stringFlavor) {
throw new UnsupportedFlavorException(flavor);
}
if (transferNodes.isEmpty()) {
return null;
}
if (transferNodes.size() == 1) {
GTreeNode node = transferNodes.get(0);
if (node instanceof NodeWithText) {
return ((NodeWithText) node).collectReportText(transferNodes, 0).trim();
}
return null;
}
return root.collectReportText(transferNodes, 0).trim();
}
@Override
public boolean isStartDragOk(List<GTreeNode> dragUserData, int dragAction) {
for (GTreeNode node : dragUserData) {
if (node instanceof NodeWithText) {
return true;
}
}
return false;
}
@Override
public int getSupportedDragActions() {
return DnDConstants.ACTION_COPY;
}
@Override
public int getSourceActions(JComponent c) {
return COPY;
}
@Override
public boolean isDropSiteOk(GTreeNode destUserData, DataFlavor[] flavors, int dropAction) {
return false;
}
@Override
public void drop(GTreeNode destUserData, Transferable transferable, int dropAction) {
throw new UnsupportedOperationException();
}
}
public ErrLogExpandableDialog(String title, String msg, MultipleCauses mc) {
this(title, msg, mc.getCauses(), null, true, true);
}
public ErrLogExpandableDialog(String title, String msg, Throwable exc) {
this(title, msg, Collections.singletonList(exc), HasConsoleText.Util.get(exc), true, true);
}
public ErrLogExpandableDialog(String title, String msg, Collection<Throwable> report) {
this(title, msg, report, null, false, false);
}
protected ErrLogExpandableDialog(String title, String msg, Collection<Throwable> report,
String console, boolean modal, boolean hasDismiss) {
super(title, modal);
hasConsole = console != null;
popup = new JPopupMenu();
JMenuItem menuCopy = new JMenuItem("Copy");
menuCopy.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME));
@ -173,13 +81,12 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
msgPanel.setLayout(new BorderLayout(16, 16));
msgPanel.setBorder(new EmptyBorder(16, 16, 16, 16));
{
JLabel msgText = new GHtmlLabel(getHTML(msg, report)) {
JLabel msgText = new GHtmlLabel(getHTML(msg, CollectionUtils.asSet(throwable))) {
@Override
public Dimension getPreferredSize() {
// when rendering HTML the label can expand larger than the screen;
// keep it reasonable
// rendering HTML the label can expand larger than the screen; keep it reasonable
Dimension size = super.getPreferredSize();
size.width = 500;
size.width = 300;
return size;
}
};
@ -206,7 +113,7 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
msgPanel.add(buttonBox, BorderLayout.EAST);
horizontalSpacer = Box.createVerticalStrut(10);
horizontalSpacer.setVisible(showingDetails | hasConsole);
horizontalSpacer.setVisible(showingDetails);
msgPanel.add(horizontalSpacer, BorderLayout.SOUTH);
}
workPanel.add(msgPanel, BorderLayout.NORTH);
@ -214,29 +121,8 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
Box workBox = Box.createVerticalBox();
{
if (hasConsole) {
JTextArea consoleText = new JTextArea(console);
JScrollPane consoleScroll =
new JScrollPane(consoleText, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED) {
@Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = 400;
dim.width = 800; // trial and error?
return dim;
}
};
consoleText.setEditable(false);
consoleText.setBackground(Color.BLACK);
consoleText.setForeground(Color.WHITE);
consoleText.setFont(Font.decode("Monospaced"));
workBox.add(consoleScroll);
}
root = new ReportRootNode(getTitle(), report);
excTree = new GTree(root) {
root = new ReportRootNode(getTitle(), CollectionUtils.asSet(throwable));
tree = new GTree(root) {
@Override
public Dimension getPreferredSize() {
@ -249,19 +135,19 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
for (GTreeNode node : CollectionUtils.asIterable(root.iterator(true))) {
if (node instanceof ReportExceptionNode) {
excTree.expandTree(node);
tree.expandTree(node);
}
}
excTree.setSelectedNode(root.getChild(0));
excTree.setVisible(showingDetails);
tree.setSelectedNode(root.getChild(0));
tree.setVisible(showingDetails);
ExcTreeTransferHandler handler = new ExcTreeTransferHandler(root);
excTree.setDragNDropHandler(handler);
excTree.setTransferHandler(handler);
ActionMap map = excTree.getActionMap();
tree.setDragNDropHandler(handler);
tree.setTransferHandler(handler);
ActionMap map = tree.getActionMap();
map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
TransferHandler.getCopyAction());
excTree.addMouseListener(new MouseAdapter() {
tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
@ -279,22 +165,19 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
}
});
workBox.add(excTree);
workBox.add(tree);
}
workPanel.add(workBox, BorderLayout.CENTER);
repack();
addWorkPanel(workPanel);
if (hasDismiss) {
addDismissButton();
}
addDismissButton();
}
private String getHTML(String msg, Collection<Throwable> report) {
//
// TODO
// Usage question: The content herein will be escaped unless you call addHTMLContenet().
// Further, clients can provide messages that contain HTML. Is there a
// use case where we want to show escaped HTML content?
@ -332,14 +215,6 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
String htmlTMsg = addBR(tMsg);
body.addElement("p").addHTMLContent(htmlTMsg);
if (t instanceof CausesImportant) { // I choose not to recurse
HTMLElement ul = body.addElement("ul");
for (Throwable ts : MultipleCauses.Util.iterCauses(t)) {
String tsMsg = getMessage(ts);
String htmlTSMsg = addBR(tsMsg);
ul.addElement("li").addHTMLContent(htmlTSMsg);
}
}
}
return html.toString();
}
@ -357,15 +232,15 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return t.getClass().getSimpleName();
}
void detailCallback() {
private void detailCallback() {
showingDetails = !showingDetails;
excTree.setVisible(showingDetails);
horizontalSpacer.setVisible(showingDetails | hasConsole);
tree.setVisible(showingDetails);
horizontalSpacer.setVisible(showingDetails);
detailButton.setText(showingDetails ? CLOSE : DETAIL);
repack();
}
void sendCallback() {
private void sendCallback() {
String details = root.collectReportText(null, 0).trim();
String title = getTitle();
close();
@ -379,6 +254,29 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return dim;
}
@Override
public void addException(String message, Throwable t) {
int n = errors.size();
if (n > MAX_EXCEPTIONS) {
return;
}
errors.add(t);
root.addNode(new ReportExceptionNode(t));
updateTitle(); // signal the new error
}
@Override
int getExceptionCount() {
return root.getChildCount();
}
@Override
public String getMessage() {
return root.getReportText();
}
static interface NodeWithText {
public String getReportText();
@ -528,9 +426,6 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
@Override
public String getReportText() {
if (exc instanceof HasConsoleText) {
return getName() + "\n" + HasConsoleText.Util.get(exc);
}
return getName();
}
@ -661,6 +556,87 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return false;
}
}
private static class ExcTreeTransferHandler extends TransferHandler
implements GTreeDragNDropHandler {
protected ReportRootNode root;
public ExcTreeTransferHandler(ReportRootNode root) {
this.root = root;
}
@Override
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> transferNodes) {
return new DataFlavor[] { DataFlavor.stringFlavor };
}
@Override
protected Transferable createTransferable(JComponent c) {
ArrayList<GTreeNode> nodes = new ArrayList<>();
for (TreePath path : ((JTree) c).getSelectionPaths()) {
nodes.add((GTreeNode) path.getLastPathComponent());
}
try {
return new StringSelection(
(String) getTransferData(nodes, DataFlavor.stringFlavor));
}
catch (UnsupportedFlavorException e) {
Msg.debug(this, e.getMessage(), e);
}
return null;
}
@Override
public Object getTransferData(List<GTreeNode> transferNodes, DataFlavor flavor)
throws UnsupportedFlavorException {
if (flavor != DataFlavor.stringFlavor) {
throw new UnsupportedFlavorException(flavor);
}
if (transferNodes.isEmpty()) {
return null;
}
if (transferNodes.size() == 1) {
GTreeNode node = transferNodes.get(0);
if (node instanceof NodeWithText) {
return ((NodeWithText) node).collectReportText(transferNodes, 0).trim();
}
return null;
}
return root.collectReportText(transferNodes, 0).trim();
}
@Override
public boolean isStartDragOk(List<GTreeNode> dragUserData, int dragAction) {
for (GTreeNode node : dragUserData) {
if (node instanceof NodeWithText) {
return true;
}
}
return false;
}
@Override
public int getSupportedDragActions() {
return DnDConstants.ACTION_COPY;
}
@Override
public int getSourceActions(JComponent c) {
return COPY;
}
@Override
public boolean isDropSiteOk(GTreeNode destUserData, DataFlavor[] flavors, int dropAction) {
return false;
}
@Override
public void drop(GTreeNode destUserData, Transferable transferable, int dropAction) {
throw new UnsupportedOperationException();
}
}
}
class TransferActionListener implements ActionListener, PropertyChangeListener {

View file

@ -15,78 +15,29 @@
*/
package docking.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.*;
import docking.ActionContext;
import docking.ComponentPlaceholder;
import docking.ComponentProvider;
import docking.DialogComponentProvider;
import docking.DockableComponent;
import docking.DockingDialog;
import docking.DockingErrorDisplay;
import docking.DockingWindowManager;
import docking.EmptyBorderToggleButton;
import docking.Tool;
import docking.*;
import docking.action.DockingActionIf;
import docking.action.ToggleDockingActionIf;
import docking.actions.DockingToolActions;
@ -104,9 +55,7 @@ import generic.test.ConcurrentTestExceptionHandler;
import generic.util.image.ImageUtils;
import ghidra.GhidraTestApplicationLayout;
import ghidra.framework.ApplicationConfiguration;
import ghidra.util.ConsoleErrorDisplay;
import ghidra.util.ErrorDisplay;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
import ghidra.util.worker.Worker;
@ -209,7 +158,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
Iterator<Window> iter = winList.iterator();
while (iter.hasNext()) {
Window w = iter.next();
if (!w.isVisible()) {
if (!w.isShowing()) {
continue;
}
String titleForWindow = getTitleForWindow(w);
@ -229,7 +178,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
Iterator<Window> iter = winList.iterator();
while (iter.hasNext()) {
Window w = iter.next();
if (!w.isVisible()) {
if (!w.isShowing()) {
continue;
}
String titleForWindow = getTitleForWindow(w);
@ -240,6 +189,14 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
return null;
}
/**
* Waits for the system error dialog to appear
* @return the dialog
*/
public static AbstractErrDialog waitForErrorDialog() {
return waitForDialogComponent(AbstractErrDialog.class);
}
public static Window waitForWindow(Class<?> windowClass) {
if ((!Dialog.class.isAssignableFrom(windowClass)) &&
@ -256,7 +213,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
Iterator<Window> it = winList.iterator();
while (it.hasNext()) {
Window w = it.next();
if (windowClass.isAssignableFrom(w.getClass()) && w.isVisible()) {
if (windowClass.isAssignableFrom(w.getClass()) && w.isShowing()) {
return w;
}
}
@ -346,7 +303,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
Set<Window> allWindows = getAllWindows();
for (Window window : allWindows) {
String windowName = window.getName();
if (name.equals(windowName) && window.isVisible()) {
if (name.equals(windowName) && window.isShowing()) {
return window;
}
@ -358,13 +315,12 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
}
/**
* Check for and display message component text associated with
* ErrLogDialog and OptionDialog windows.
* Check for and display message component text associated with OptionDialog windows
* @param w any window
* @return the message string if one can be found; <code>null</code> otherwise
*/
public static String checkMessageDisplay(Window w) {
Component c = findComponentByName(w, "MESSAGE-COMPONENT");
public static String getMessageText(Window w) {
Component c = findComponentByName(w, OptionDialog.MESSAGE_COMPONENT_NAME);
if (c instanceof JLabel) {
return ((JLabel) c).getText();
}
@ -466,7 +422,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
// note: we use System.err here to get more obvious errors in the console
String title = getDebugTitleForWindow(window);
System.err.println("DockingTestCase - Forced window closure: " + title);
String errorMessage = checkMessageDisplay(window);
String errorMessage = getMessageText(window);
if (errorMessage != null) {
System.err.println("\tWindow error message: " + errorMessage);
}
@ -541,7 +497,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
Iterator<Window> iter = winList.iterator();
while (iter.hasNext()) {
Window w = iter.next();
if ((w instanceof JDialog) && w.isVisible()) {
if ((w instanceof JDialog) && w.isShowing()) {
String windowTitle = getTitleForWindow(w);
if (title.equals(windowTitle)) {
return (JDialog) w;
@ -576,7 +532,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
Iterator<Window> iter = winList.iterator();
while (iter.hasNext()) {
Window w = iter.next();
if ((w instanceof JDialog) && w.isVisible()) {
if ((w instanceof JDialog) && w.isShowing()) {
String windowTitle = getTitleForWindow(w);
if (title.equals(windowTitle)) {
return (JDialog) w;
@ -722,10 +678,6 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
return null;
}
if (!window.isVisible()) {
return null;
}
if (!window.isShowing()) {
return null;
}
@ -1132,8 +1084,10 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
public static Set<DockingActionIf> getActionsByOwnerAndName(Tool tool, String owner,
String name) {
Set<DockingActionIf> ownerActions = tool.getDockingActionsByOwnerName(owner);
return ownerActions.stream().filter(action -> action.getName().equals(name)).collect(
Collectors.toSet());
return ownerActions.stream()
.filter(action -> action.getName().equals(name))
.collect(
Collectors.toSet());
}
/**

View file

@ -99,7 +99,7 @@ import ghidra.util.exception.AssertException;
* @see OptionDialogBuilder
*/
public class OptionDialog extends DialogComponentProvider {
private static final String MESSAGE_COMPONENT_NAME = "MESSAGE-COMPONENT";
public static final String MESSAGE_COMPONENT_NAME = "MESSAGE-COMPONENT";
/** Used for error messages. */
public static final int ERROR_MESSAGE = 0;
/** Used for information messages. */

View file

@ -102,7 +102,7 @@ public class ScrollableTextArea extends JScrollPane {
}
/**
* Appends the text to the text area maintained in this scrollpane
* Appends the text to the text area maintained in this scroll pane
* @param text the text to append.
*/
public void append(String text) {
@ -111,13 +111,15 @@ public class ScrollableTextArea extends JScrollPane {
/**
* Returns the number of lines current set in the text area
* @return the count
*/
public int getLineCount() {
return textArea.getLineCount();
}
/**
* Returns the tabsize set in the text area
* Returns the tab size set in the text area
* @return the size
*/
public int getTabSize() {
return textArea.getTabSize();
@ -125,6 +127,7 @@ public class ScrollableTextArea extends JScrollPane {
/**
* Returns the total area height of the text area (row height * line count)
* @return the height
*/
public int getTextAreaHeight() {
return (textArea.getAreaHeight());
@ -132,6 +135,7 @@ public class ScrollableTextArea extends JScrollPane {
/**
* Returns the visible height of the text area
* @return the height
*/
public int getTextVisibleHeight() {
return textArea.getVisibleHeight();
@ -200,6 +204,7 @@ public class ScrollableTextArea extends JScrollPane {
/**
* Returns the text contained within the text area
* @return the text
*/
public String getText() {
return textArea.getText();

View file

@ -0,0 +1,55 @@
/* ###
* 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.util.table.column;
import java.awt.Component;
import java.util.Date;
import javax.swing.JLabel;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.docking.settings.Settings;
import ghidra.util.DateUtils;
/**
* A renderer for clients that wish to display a {@link Date} as a timestamp with the
* date and time.
*/
public class DefaultTimestampRenderer extends AbstractGColumnRenderer<Date> {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel label = (JLabel) super.getTableCellRendererComponent(data);
Date value = (Date) data.getValue();
if (value != null) {
label.setText(DateUtils.formatDateTimestamp(value));
}
return label;
}
@Override
public String getFilterString(Date t, Settings settings) {
return DateUtils.formatDateTimestamp(t);
}
@Override
public ColumnConstraintFilterMode getColumnConstraintFilterMode() {
// This allows for text filtering in the table and date filtering on columns
return ColumnConstraintFilterMode.ALLOW_ALL_FILTERS;
}
}

View file

@ -35,7 +35,7 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
DockingErrorDisplay display = new DockingErrorDisplay();
DefaultErrorLogger logger = new DefaultErrorLogger();
Exception exception = new Exception("My test exception");
doDisplay(display, logger, exception);
reportException(display, logger, exception);
assertErrLogDialog();
}
@ -46,11 +46,29 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
DefaultErrorLogger logger = new DefaultErrorLogger();
Exception nestedException = new Exception("My nested test exception");
Exception exception = new Exception("My test exception", nestedException);
doDisplay(display, logger, exception);
reportException(display, logger, exception);
assertErrLogDialog();
}
@Test
public void testDefaultErrorDisplay_MultipleAsynchronousExceptions() {
DockingErrorDisplay display = new DockingErrorDisplay();
DefaultErrorLogger logger = new DefaultErrorLogger();
Exception exception = new Exception("My test exception");
reportException(display, logger, exception);
ErrLogDialog dialog = getErrLogDialog();
assertExceptionCount(dialog, 1);
reportException(display, logger, new NullPointerException("It is null!"));
assertExceptionCount(dialog, 2);
close(dialog);
}
@Test
public void testMultipleCausesErrorDisplay() {
DockingErrorDisplay display = new DockingErrorDisplay();
@ -58,43 +76,51 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
Throwable firstCause = new Exception("My test exception - first cause");
MultipleCauses exception = new MultipleCauses(Collections.singletonList(firstCause));
doDisplay(display, logger, exception);
reportException(display, logger, exception);
assertErrLogExpandableDialog();
ErrLogExpandableDialog dialog = assertErrLogExpandableDialog();
assertExceptionCount(dialog, 1);
reportException(display, logger, new NullPointerException("It is null!"));
assertExceptionCount(dialog, 2);
close(dialog);
}
private void assertErrLogExpandableDialog() {
Window w = waitForWindow(TEST_TITLE, 2000);
assertNotNull(w);
private void assertExceptionCount(AbstractErrDialog errDialog, int n) {
final ErrLogExpandableDialog errDialog =
int actual = errDialog.getExceptionCount();
assertEquals(n, actual);
}
private ErrLogExpandableDialog assertErrLogExpandableDialog() {
Window w = waitForWindow(TEST_TITLE);
ErrLogExpandableDialog errDialog =
getDialogComponentProvider(w, ErrLogExpandableDialog.class);
assertNotNull(errDialog);
runSwing(new Runnable() {
@Override
public void run() {
errDialog.close();
}
});
return errDialog;
}
private void assertErrLogDialog() {
Window w = waitForWindow(TEST_TITLE, 2000);
Window w = waitForWindow(TEST_TITLE);
assertNotNull(w);
final ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
assertNotNull(errDialog);
runSwing(new Runnable() {
@Override
public void run() {
errDialog.close();
}
});
close(errDialog);
}
private void doDisplay(final DockingErrorDisplay display, final DefaultErrorLogger logger,
private ErrLogDialog getErrLogDialog() {
Window w = waitForWindow(TEST_TITLE);
assertNotNull(w);
ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
assertNotNull(errDialog);
return errDialog;
}
private void reportException(final DockingErrorDisplay display, final DefaultErrorLogger logger,
final Throwable throwable) {
runSwing(new Runnable() {
@Override