diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java index e85c94a283..077da0aaf3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java @@ -1513,12 +1513,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl return Math.max(dominantMonitor.getProgress(), slaveMonitor.getProgress()); } - @Override - public void reportIssue(Issue issue) { - dominantMonitor.reportIssue(issue); - slaveMonitor.reportIssue(issue); - } - @Override public void cancel() { dominantMonitor.cancel(); @@ -1535,16 +1529,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl dominantMonitor.addCancelledListener(listener); } - @Override - public void addIssueListener(IssueListener listener) { - dominantMonitor.addIssueListener(listener); - } - - @Override - public void removeIssueListener(IssueListener listener) { - dominantMonitor.removeIssueListener(listener); - } - @Override public void setCancelEnabled(boolean enable) { dominantMonitor.setCancelEnabled(enable); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessTimedTaskMonitor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessTimedTaskMonitor.java index f9f9b2dbf9..1026fec578 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessTimedTaskMonitor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessTimedTaskMonitor.java @@ -18,9 +18,9 @@ package ghidra.app.util.headless; import java.util.Timer; import java.util.TimerTask; -import ghidra.util.Issue; import ghidra.util.exception.CancelledException; -import ghidra.util.task.*; +import ghidra.util.task.CancelledListener; +import ghidra.util.task.TaskMonitor; /** * Monitor used by Headless Analyzer for "timeout" functionality @@ -99,11 +99,6 @@ public class HeadlessTimedTaskMonitor implements TaskMonitor { return 0; } - @Override - public void reportIssue(Issue issue) { - // stub - } - @Override public void cancel() { timer.cancel(); // Terminate the timer thread @@ -120,16 +115,6 @@ public class HeadlessTimedTaskMonitor implements TaskMonitor { // stub } - @Override - public void addIssueListener(IssueListener listener) { - // stub - } - - @Override - public void removeIssueListener(IssueListener listener) { - // stub - } - @Override public void setCancelEnabled(boolean enable) { // stub diff --git a/Ghidra/Features/Base/src/test/java/ghidra/util/task/TaskMonitorServiceTest.java b/Ghidra/Features/Base/src/test/java/ghidra/util/task/TaskMonitorServiceTest.java new file mode 100644 index 0000000000..4e70cb7407 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/util/task/TaskMonitorServiceTest.java @@ -0,0 +1,341 @@ +/* ### + * 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.task; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.*; + +import org.junit.*; + +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.util.SystemUtilities; +import ghidra.util.exception.CancelledException; + +/** + * Tests for the {@link TaskMonitorService} + */ +public class TaskMonitorServiceTest extends AbstractGhidraHeadedIntegrationTest { + + @Before + public void setup() { + // nothing to do + } + + @After + public void teardown() { + // nothing to do + } + + /** + * Verifies that in a single-threaded environment, the {@link TaskMonitorService} + * returns the correct instance of {@link TaskMonitor}. In this case, the initial + * monitor is registered by the {@link TaskLauncher task launcher}; the next call + * to {@link TaskMonitorService#getMonitor() getMonitor} should return the same. + */ + @Test + public void testSingleThread() { + + TaskLauncher.launch(new Task("task1") { + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + + TaskMonitor newMonitor = TaskMonitorService.getMonitor(); + assertSame(newMonitor, monitor); + } + + }); + + waitForTasks(); + } + + /** + * Verifies that in a single-threaded environment, the {@link TaskMonitorService} + * returns the same instance of {@link TaskMonitor} each time one is + * requested. + *
+ * Note that the first time a monitor is requested it will always return the primary
+ * monitor that allows progress changes. Each subsequent time it will return the
+ * secondary monitor; these secondary monitors is what this test is verifying.
+ */
+ @Test
+ public void testSingleThreadSecondaryMonitors() {
+
+ TaskLauncher.launch(new Task("task1") {
+
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+
+ // First monitor requested is always the primary - just make the call so
+ // we can get to retrieving some secondary monitors
+ TaskMonitorService.getMonitor();
+
+ TaskMonitor secondaryMonitor1 = TaskMonitorService.getMonitor();
+ TaskMonitor secondaryMonitor2 = TaskMonitorService.getMonitor();
+
+ assertEquals(secondaryMonitor1, secondaryMonitor2);
+ }
+
+ });
+
+ waitForTasks();
+ }
+
+ /**
+ * Verifies that in a multi-threaded environment, the {@link TaskMonitorService}
+ * returns a different instance of {@link TaskMonitor} for each thread.
+ */
+ @Test
+ public void testMultipleThreads() {
+
+ AtomicReference
+ * Note: The first monitor is registered behind the scenes by the task launcher
+ */
+ @Test
+ public void testRegisterMultipleMonitors() {
+
+ TaskLauncher.launch(new Task("task") {
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+
+ try {
+ TaskMonitorService.register(TaskMonitor.DUMMY);
+ }
+ catch (Exception ex) {
+ // expected catch
+ return;
+ }
+
+ fail("Successful register of new monitor (should not have been allowed)");
+ }
+ });
+
+ waitForTasks();
+ }
+
+ /**
+ * Verifies that unique monitor id's are correctly assigned to each thread that registers
+ * a monitor
+ */
+ @Test
+ public void testMonitorIds() {
+
+ AtomicInteger monitor1Id = new AtomicInteger();
+ AtomicInteger monitor2Id = new AtomicInteger();
+ AtomicInteger monitor3Id = new AtomicInteger();
+
+ Thread thread1 =
+ new Thread(() -> monitor1Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
+
+ Thread thread2 =
+ new Thread(() -> monitor2Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
+
+ Thread thread3 =
+ new Thread(() -> monitor3Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
+
+ thread1.start();
+ thread2.start();
+ thread3.start();
+
+ waitForTasks();
+
+ boolean areIdsDifferent =
+ (monitor1Id.get() != monitor2Id.get()) && (monitor2Id.get() != monitor3Id.get());
+ assertTrue(areIdsDifferent);
+ }
+
+ /**
+ * Verifies that if a client attempts to remove a monitor with an invalid id the request
+ * will fail
+ */
+ @Test
+ public void testRemoveMonitorFail() {
+
+ AtomicBoolean removed = new AtomicBoolean(true);
+
+ // All monitor id's are positive integers; this is guaranteed to generated a failure
+ final int BOGUS_ID = -1;
+
+ Thread thread1 = new Thread(() -> {
+ TaskMonitorService.register(TaskMonitor.DUMMY);
+ try {
+ TaskMonitorService.remove(BOGUS_ID);
+ }
+ catch (Exception e) {
+ // expected catch
+ removed.set(false);
+ }
+ });
+
+ thread1.start();
+
+ waitForTasks();
+
+ assertFalse(removed.get());
+ }
+
+ /**
+ * Verifies that a monitor can be successfully removed given a correct id
+ */
+ @Test
+ public void testRemoveMonitorSuccess() {
+
+ AtomicBoolean removed = new AtomicBoolean(false);
+
+ AtomicInteger monitorId = new AtomicInteger();
+
+ Thread thread1 = new Thread(() -> {
+ monitorId.set(TaskMonitorService.register(TaskMonitor.DUMMY));
+
+ try {
+ TaskMonitorService.remove(monitorId.get());
+ removed.set(true);
+ }
+ catch (Exception e) {
+ // should not be here
+ fail();
+ }
+ });
+
+ thread1.start();
+
+ waitForTasks();
+
+ assertTrue(removed.get());
+ }
+
+ /**
+ * Verifies that the first monitor returned from the service will be the
+ * primary monitor
+ */
+ @Test
+ public void testRetrievePrimaryMonitor() {
+
+ TaskLauncher.launch(new Task("task") {
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+
+ TaskMonitor monitor1 = TaskMonitorService.getMonitor();
+ assertTrue(monitor1 instanceof TaskDialog);
+ }
+ });
+
+ waitForTasks();
+ }
+
+ /**
+ * Verifies that any subsequent requests after the initial request will result in
+ * a {@link SecondaryTaskDialog} being returned
+ */
+ @Test
+ public void testRetrieveSecondaryMonitor() {
+
+ TaskLauncher.launch(new Task("task") {
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+
+ TaskMonitor monitor1 = TaskMonitorService.getMonitor();
+ assertTrue(monitor1 instanceof TaskDialog);
+
+ monitor1 = TaskMonitorService.getMonitor();
+ assertTrue(monitor1 instanceof SecondaryTaskMonitor);
+ }
+ });
+
+ waitForTasks();
+ }
+
+ /**
+ * Verifies that once a monitor has been reset, the next call to retrieve a monitor will
+ * return it as a primary
+ */
+ @Test
+ public void testMonitorReset() {
+
+ TaskLauncher.launch(new Task("task") {
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+
+ TaskMonitor monitor1 = TaskMonitorService.getMonitor();
+ assertTrue(monitor1 instanceof TaskDialog);
+
+ monitor1 = TaskMonitorService.getMonitor();
+ assertTrue(monitor1 instanceof SecondaryTaskMonitor);
+
+ monitor1.reset();
+
+ monitor1 = TaskMonitorService.getMonitor();
+ assertTrue(monitor1 instanceof TaskDialog);
+ }
+ });
+
+ waitForTasks();
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
index da56f6ec0e..cbd2399f02 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
@@ -67,6 +67,7 @@ public class DialogComponentProvider
protected JButton dismissButton;
private boolean isAlerting;
private JLabel statusLabel;
+ private JLabel subStatusLabel;
private JPanel statusProgPanel; // contains status panel and progress panel
private Timer showTimer;
private TaskScheduler taskScheduler;
@@ -608,6 +609,11 @@ public class DialogComponentProvider
public void setStatusText(String text) {
setStatusText(text, MessageType.INFO);
}
+
+ @Override
+ public void setSubStatusText(String text) {
+ setSubStatusText(text, MessageType.INFO);
+ }
/**
* Sets the text in the dialog's status line using the specified message type to control
@@ -621,12 +627,24 @@ public class DialogComponentProvider
setStatusText(message, type, false);
}
+ @Override
+ public void setSubStatusText(String message, MessageType type) {
+ setSubStatusText(message, type, false);
+ }
+
@Override
public void setStatusText(String message, MessageType type, boolean alert) {
String text = StringUtils.isBlank(message) ? " " : message;
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert));
}
+
+ @Override
+ public void setSubStatusText(String message, MessageType type, boolean alert) {
+
+ String text = StringUtils.isBlank(message) ? " " : message;
+ SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetSubStatusText(text, type, alert));
+ }
private void doSetStatusText(String text, MessageType type, boolean alert) {
@@ -641,6 +659,20 @@ public class DialogComponentProvider
alertMessage();
}
}
+
+ private void doSetSubStatusText(String text, MessageType type, boolean alert) {
+
+ SystemUtilities.assertThisIsTheSwingThread(
+ "Setting text must be performed on the Swing thread");
+
+ subStatusLabel.setText(text);
+ subStatusLabel.setForeground(getStatusColor(type));
+ updateStatusToolTip();
+
+ if (alert) {
+ alertMessage();
+ }
+ }
/**
* Signals for this dialog to visually draw the user's attention to the status text
@@ -679,11 +711,13 @@ public class DialogComponentProvider
// normal Swing mechanism may not have yet happened).
mainPanel.validate();
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
+ subStatusLabel.setVisible(false);
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
animator.addTarget(new TimingTargetAdapter() {
@Override
public void end() {
statusLabel.setVisible(true);
+ subStatusLabel.setVisible(true);
alertFinishedCallback.call();
isAlerting = false;
}
@@ -755,12 +789,7 @@ public class DialogComponentProvider
private void showProgressBar(String localTitle, boolean hasProgress, boolean canCancel) {
taskMonitorComponent.setTaskName(localTitle);
taskMonitorComponent.showProgress(hasProgress);
- if (canCancel) {
- taskMonitorComponent.showCancelButton(true);
- }
- else {
- taskMonitorComponent.showCancelButton(false);
- }
+ taskMonitorComponent.setCancelButtonVisibility(canCancel);
progressCardLayout.show(statusProgPanel, PROGRESS);
rootPanel.validate();
}
@@ -799,18 +828,29 @@ public class DialogComponentProvider
public void clearStatusText() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
statusLabel.setText(" ");
+ subStatusLabel.setText(" ");
updateStatusToolTip();
});
}
/**
- * returns the current status in the dialogs status line=
+ * Returns the current status in the dialogs status line
+ *
* @return the status text
*/
public String getStatusText() {
return statusLabel.getText();
}
+ /**
+ * Returns the secondary status message
+ *
+ * @return the secondary status message
+ */
+ public String getSubStatusText() {
+ return subStatusLabel.getText();
+ }
+
protected JLabel getStatusLabel() {
return statusLabel;
}
@@ -901,6 +941,14 @@ public class DialogComponentProvider
updateStatusToolTip();
}
});
+
+ subStatusLabel = new JLabel();
+ subStatusLabel.setName("subStatusLabel");
+ subStatusLabel.setHorizontalAlignment(SwingConstants.CENTER);
+ subStatusLabel.setForeground(Color.blue);
+ subStatusLabel.setFont(subStatusLabel.getFont().deriveFont(Font.ITALIC));
+ subStatusLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ subStatusLabel.setFont(subStatusLabel.getFont().deriveFont(11.0f));
// use a strut panel so the size of the message area does not change if we make
// the message label not visible
@@ -908,6 +956,7 @@ public class DialogComponentProvider
panel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
panel.add(statusLabel, BorderLayout.CENTER);
+ panel.add(subStatusLabel, BorderLayout.SOUTH);
return panel;
}
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/ChompingBitsAnimationPanel.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/ChompingBitsAnimationPanel.java
new file mode 100644
index 0000000000..e37b9af018
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/ChompingBitsAnimationPanel.java
@@ -0,0 +1,48 @@
+/* ###
+ * 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.task;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.*;
+
+import docking.util.AnimatedIcon;
+import resources.ResourceManager;
+
+/**
+ * Panel that displays an animation of the Ghidra dragon chomping bits.
+ */
+public class ChompingBitsAnimationPanel extends JPanel {
+
+ public ChompingBitsAnimationPanel() {
+ setLayout(new BorderLayout());
+
+ List
+ * Note: Two instances of this class are deemed equal if they have the same {@link #parentMonitor},
+ * hence the override of {@link #hashCode()} and {@link #equals(Object)}.
+ */
+public class SecondaryTaskMonitor implements TaskMonitor {
+
+ private TaskMonitor parentMonitor;
+
+ /**
+ * Constructor
+ *
+ * @param parentMonitor the fully-functional task monitor this is based off of
+ */
+ public SecondaryTaskMonitor(TaskMonitor parentMonitor) {
+ this.parentMonitor = parentMonitor;
+ }
+
+ /**
+ * Overridden to ensure that clients who have this type of monitor will only update the
+ * secondary message when using this method
+ *
+ * @param message the message string to display
+ */
+ @Override
+ public void setMessage(String message) {
+ if (parentMonitor instanceof TaskDialog) {
+ ((TaskDialog) parentMonitor).setSecondaryMessage(message);
+ }
+ parentMonitor.setMessage(message);
+ }
+
+ @Override
+ public void setCancelEnabled(boolean enable) {
+ parentMonitor.setCancelEnabled(enable);
+ }
+
+ @Override
+ public void setInitialized(boolean init) {
+ parentMonitor.setInitialized(init);
+ }
+
+ @Override
+ public boolean isCancelEnabled() {
+ return parentMonitor.isCancelEnabled();
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return parentMonitor.isCancelled();
+ }
+
+ @Override
+ public synchronized void cancel() {
+ parentMonitor.cancel();
+ }
+
+ @Override
+ public synchronized void clearCanceled() {
+ parentMonitor.clearCanceled();
+ }
+
+ @Override
+ public void checkCanceled() throws CancelledException {
+ parentMonitor.checkCanceled();
+ }
+
+ @Override
+ public void addCancelledListener(CancelledListener listener) {
+ parentMonitor.addCancelledListener(listener);
+ }
+
+ @Override
+ public void removeCancelledListener(CancelledListener listener) {
+ parentMonitor.removeCancelledListener(listener);
+ }
+
+ @Override
+ public long getMaximum() {
+ return parentMonitor.getMaximum();
+ }
+
+ @Override
+ public long getProgress() {
+ return parentMonitor.getProgress();
+ }
+
+ @Override
+ public void setShowProgressValue(boolean showProgressValue) {
+ // squash
+ }
+
+ @Override
+ public void setProgress(long value) {
+ // squash
+ }
+
+ @Override
+ public void initialize(long max) {
+ // squash
+ }
+
+ @Override
+ public void setMaximum(long max) {
+ // squash
+ }
+
+ @Override
+ public void setIndeterminate(boolean indeterminate) {
+ // squash
+ }
+
+ @Override
+ public void incrementProgress(long incrementAmount) {
+ // squash
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((parentMonitor == null) ? 0 : parentMonitor.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SecondaryTaskMonitor other = (SecondaryTaskMonitor) obj;
+ if (!Objects.equals(parentMonitor, other.parentMonitor)) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskDialog.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskDialog.java
index f251765ac8..836ca37e8d 100644
--- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskDialog.java
@@ -15,20 +15,20 @@
*/
package ghidra.util.task;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
-import docking.util.AnimatedIcon;
import docking.widgets.OptionDialog;
-import docking.widgets.label.GIconLabel;
-import ghidra.util.*;
+import ghidra.util.HelpLocation;
+import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.timer.GTimer;
-import resources.ResourceManager;
/**
* Dialog that is displayed to show activity for a Task that is running outside of the
@@ -36,25 +36,48 @@ import resources.ResourceManager;
*/
public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
+ /** Timer used to give the task a chance to complete */
private static final int SLEEPY_TIME = 10;
+
+ /** Amount of time to wait before showing the monitor dialog */
private final static int MAX_DELAY = 200000;
+
public final static int DEFAULT_WIDTH = 275;
private Timer showTimer;
- private TaskMonitorComponent monitorComponent;
private AtomicInteger taskID = new AtomicInteger();
- private boolean canCancel;
- private Runnable updateMessage;
private Runnable closeDialog;
- private Runnable enableCancelButton;
- private String newMessage;
- private boolean cancelState = true;
private Component centerOnComp;
private Runnable shouldCancelRunnable;
- private boolean done;
+ private boolean taskDone;
private JPanel mainPanel;
+ private ChompingBitsAnimationPanel chompingBitsPanel;
+ private TaskMonitorComponent monitorComponent;
- /** Creates new TaskDialog
+ /** Runnable that updates the primary message label in the dialog */
+ private Runnable updatePrimaryMessageRunnable;
+
+ /** Runnable that updates the secondary message label in the dialog */
+ private Runnable updateSecondaryMessageRunnable;
+
+ /** If not null, then the value of the string has yet to be rendered */
+ private String newPrimaryMessage;
+
+ /** If not null, then the value of the string has yet to be rendered */
+ private String newSecondaryMessage;
+
+ /**
+ * Indicates if this monitor has been initialized for progress updates. If this value
+ * is set to true, the {@link TaskMonitorService} will not return the monitor to
+ * another caller (only one client should be able to update progress at a time).
+ */
+ private AtomicBoolean initialized = new AtomicBoolean(false);
+
+ private SecondaryTaskMonitor secondaryTaskMonitor;
+
+ /**
+ * Constructor
+ *
* @param centerOnComp component to be centered over when shown,
* otherwise center over parent. If both centerOnComp and parent
* are null, dialog will be centered on screen.
@@ -65,7 +88,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
task.hasProgress());
}
- /** Creates a new TaskDialog.
+ /**
+ * Constructor
+ *
* @param task the Task that this dialog will be associated with
*/
public TaskDialog(Task task) {
@@ -73,18 +98,20 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
}
/**
- * Construct new TaskDialog.
+ * Constructor
+ *
* @param title title for the dialog
* @param canCancel true if the task can be canceled
* @param isModal true if the dialog should be modal
* @param hasProgress true if the dialog should show a progress bar
*/
public TaskDialog(String title, boolean canCancel, boolean isModal, boolean hasProgress) {
- this(null, title, isModal, canCancel, true /*hasProgress*/);
+ this(null, title, isModal, canCancel, hasProgress);
}
-
+
/**
- * Construct new TaskDialog.
+ * Constructor
+ *
* @param centerOnComp component to be centered over when shown, otherwise center over
* parent. If both centerOnComp is null, then the active window will be used
* @param title title for the dialog
@@ -99,8 +126,21 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
setup(canCancel, hasProgress);
}
+ @Override
+ public boolean isInitialized() {
+ return initialized.get();
+ }
+
+ @Override
+ public void setInitialized(boolean init) {
+ this.initialized.set(init);
+ }
+
private void setup(boolean canCancel, boolean hasProgress) {
- this.canCancel = canCancel;
+ monitorComponent = new TaskMonitorComponent(false, false);
+ chompingBitsPanel = new ChompingBitsAnimationPanel();
+
+ setCancelEnabled(canCancel);
setRememberLocation(false);
setRememberSize(false);
setTransient(true);
@@ -108,13 +148,18 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
close();
dispose();
};
- updateMessage = () -> {
- setStatusText(newMessage);
+ updatePrimaryMessageRunnable = () -> {
+ setStatusText(newPrimaryMessage);
synchronized (TaskDialog.this) {
- newMessage = null;
+ newPrimaryMessage = null;
+ }
+ };
+ updateSecondaryMessageRunnable = () -> {
+ setSubStatusText(newSecondaryMessage);
+ synchronized (TaskDialog.this) {
+ newSecondaryMessage = null;
}
};
- enableCancelButton = () -> TaskDialog.super.setCancelEnabled(cancelState);
shouldCancelRunnable = () -> {
int currentTaskID = taskID.get();
@@ -124,9 +169,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
}
};
- monitorComponent = new TaskMonitorComponent(false, false);
mainPanel = new JPanel(new BorderLayout());
addWorkPanel(mainPanel);
+
if (hasProgress) {
installProgressMonitor();
}
@@ -142,6 +187,11 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
setHelpLocation(new HelpLocation("Tool", "TaskDialog"));
}
+ /**
+ * Shows a dialog asking the user if they really, really want to cancel the task
+ *
+ * @return true if the task should be cancelled
+ */
protected boolean promptToVerifyCancel() {
boolean userSaysYes = OptionDialog.showYesNoDialog(getComponent(), "Cancel?",
"Do you really want to cancel \"" + getTitle() + "\"?") == OptionDialog.OPTION_ONE;
@@ -150,34 +200,24 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
}
/**
- * Creates the main work panel for the dialog
+ * Adds the panel that contains the progress bar to the dialog
*/
private void installProgressMonitor() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
mainPanel.removeAll();
-
- JPanel panel = new JPanel(new BorderLayout());
- panel.setBorder(BorderFactory.createEmptyBorder(20, 10, 5, 10));
- panel.add(monitorComponent);
- mainPanel.add(panel, BorderLayout.NORTH);
-
+ mainPanel.add(monitorComponent, BorderLayout.CENTER);
repack();
});
}
+ /**
+ * Adds the panel that contains the chomping bits animation to the dialog. This should only be
+ * called if the dialog has no need to display progress.
+ */
private void installActivityDisplay() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
mainPanel.removeAll();
-
- JPanel panel = new JPanel(new BorderLayout());
- panel.setSize(new Dimension(200, 100));
- String[] filenames = { "images/eatbits1.png", "images/eatbits2.png",
- "images/eatbits3.png", "images/eatbits4.png", "images/eatbits5.png",
- "images/eatbits6.png", "images/eatbits7.png" };
- panel.add(
- new GIconLabel(new AnimatedIcon(ResourceManager.loadImages(filenames), 200, 0)));
- mainPanel.add(panel, BorderLayout.CENTER);
-
+ mainPanel.add(chompingBitsPanel, BorderLayout.CENTER);
repack();
});
}
@@ -200,7 +240,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
monitorComponent.setShowProgressValue(showProgressValue);
}
- /** Sets the percentage done.
+ /**
+ * Sets the percentage done
+ *
* @param param The percentage of the task completed.
*/
@Override
@@ -250,62 +292,58 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
monitorComponent.setIndeterminate(indeterminate);
}
- /** Called if the user presses the cancel button on
- * the dialog
- */
@Override
protected void cancelCallback() {
- synchronized (this) {
- if (!monitorComponent.isCancelEnabled() || monitorComponent.isCancelled()) {
- return;
- }
- }
-
SwingUtilities.invokeLater(shouldCancelRunnable);
}
- /** Sets the message in the TaskDialog dialog
- * @param str The message string to be displayed
- */
@Override
synchronized public void setMessage(String str) {
- boolean invoke = (newMessage == null);
- newMessage = str;
+ boolean invoke = (newPrimaryMessage == null);
if (invoke) {
- SwingUtilities.invokeLater(updateMessage);
+ newPrimaryMessage = str;
+ SwingUtilities.invokeLater(updatePrimaryMessageRunnable);
}
}
/**
- * Set the enable state of the Cancel button.
- * @param enable the state to set the cancel button.
+ * Updates the secondary message on the task monitor
+ *
+ * @param str the string to update
*/
- @Override
- public void setCancelEnabled(boolean enable) {
- if (canCancel) {
- monitorComponent.setCancelEnabled(enable);
- SwingUtilities.invokeLater(enableCancelButton);
+ synchronized public void setSecondaryMessage(String str) {
+ boolean invoke = (newSecondaryMessage == null);
+ if (invoke) {
+ newSecondaryMessage = str;
+ SwingUtilities.invokeLater(updateSecondaryMessageRunnable);
}
}
+ @Override
+ public void setCancelEnabled(boolean enable) {
+ monitorComponent.setCancelEnabled(enable);
+ super.setCancelEnabled(enable);
+ }
+
@Override
public boolean isCancelEnabled() {
- return canCancel && cancelState;
+ return super.isCancelEnabled();
}
public synchronized void taskProcessed() {
- done = true;
+ taskDone = true;
monitorComponent.notifyChangeListeners();
SwingUtilities.invokeLater(closeDialog);
}
+ @Override
public synchronized void reset() {
- done = false;
+ taskDone = false;
taskID.incrementAndGet();
}
public synchronized boolean isCompleted() {
- return done;
+ return taskDone;
}
@Override
@@ -393,7 +431,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
@Override
public synchronized void cancel() {
- if (!canCancel || monitorComponent.isCancelled()) {
+ if (monitorComponent.isCancelled()) {
return;
}
// Mark as cancelled, must be detected by task which should terminate
@@ -432,18 +470,11 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
}
@Override
- public void addIssueListener(IssueListener listener) {
- monitorComponent.addIssueListener(listener);
+ public synchronized TaskMonitor getSecondaryMonitor() {
+ if (secondaryTaskMonitor == null) {
+ secondaryTaskMonitor = new SecondaryTaskMonitor(this);
+ }
+ return secondaryTaskMonitor;
}
- @Override
- public void removeIssueListener(IssueListener listener) {
- monitorComponent.removeIssueListener(listener);
-
- }
-
- @Override
- public void reportIssue(Issue issue) {
- monitorComponent.reportIssue(issue);
- }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java
index f132759d8e..4aba7f8b8b 100644
--- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java
@@ -24,75 +24,79 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*;
import docking.ToolTipManager;
-import docking.util.AnimatedIcon;
import docking.widgets.EmptyBorderButton;
import docking.widgets.OptionDialog;
import docking.widgets.label.GDHtmlLabel;
-import docking.widgets.label.GIconLabel;
-import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.layout.VerticalLayout;
import resources.Icons;
-import resources.ResourceManager;
/**
* Component that contains a progress bar, a progress icon, and a cancel
* button to cancel the task that is associated with this task monitor.
+ *
* By default the progress bar and progress icon (spinning globe) are visible.
*/
public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private WeakSet
+ * Note that the monitor is handed to the {@link TaskMonitorService} here so it will be
+ * available to any users request it via {@link TaskMonitorService#getMonitor() getMonitor}.
*
- * @param monitor The TaskMonitor
+ * @param monitor the task monitor
*/
@Override
public final void monitoredRun(TaskMonitor monitor) {
this.taskMonitor = monitor;
+ int monitorId = TaskMonitorService.register(monitor);
+
// this will be removed from SystemUtilities in Task.run() after the task is finished
TaskUtilities.addTrackedTask(this, monitor);
@@ -136,9 +141,14 @@ public abstract class Task implements MonitoredRunnable {
getTaskTitle() + " - Uncaught Exception: " + t.toString(), t);
}
finally {
- // this is put into SystemUtilities by the TaskLauncher
+
+ // This should not be necessary since the thread local object will be cleaned up
+ // by the GC when the thread terminates, but it's here in case we ever use this
+ // in conjunction with thread pools and have to manually remove them.
+ TaskMonitorService.remove(monitorId);
+
TaskUtilities.removeTrackedTask(this);
- this.taskMonitor = null;
+ this.taskMonitor = null;
}
notifyTaskListeners(isCancelled);
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorAdapter.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorAdapter.java
index 31781489d7..2a68b53d0e 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorAdapter.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorAdapter.java
@@ -15,7 +15,6 @@
*/
package ghidra.util.task;
-import ghidra.util.Issue;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
@@ -163,19 +162,4 @@ public class TaskMonitorAdapter implements TaskMonitor {
public synchronized void removeCancelledListener(CancelledListener listener) {
listeners.remove(listener);
}
-
- @Override
- public void addIssueListener(IssueListener listener) {
- // do nothing
- }
-
- @Override
- public void removeIssueListener(IssueListener listener) {
- // do nothing
- }
-
- @Override
- public void reportIssue(Issue issue) {
- // do nothing
- }
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorSplitter.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorSplitter.java
index e893e7a766..f62153d403 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorSplitter.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TaskMonitorSplitter.java
@@ -19,7 +19,6 @@ import java.util.HashSet;
import java.util.Set;
import generic.concurrent.ConcurrentListenerSet;
-import ghidra.util.Issue;
import ghidra.util.exception.CancelledException;
public class TaskMonitorSplitter {
@@ -176,20 +175,5 @@ public class TaskMonitorSplitter {
listener.cancelled();
}
}
-
- @Override
- public void addIssueListener(IssueListener listener) {
- parent.addIssueListener(listener);
- }
-
- @Override
- public void removeIssueListener(IssueListener listener) {
- parent.removeIssueListener(listener);
- }
-
- @Override
- public void reportIssue(Issue issue) {
- parent.reportIssue(issue);
- }
}
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java
index e5cf10f7ed..d2598da787 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java
@@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import ghidra.generic.function.Callback;
-import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.TimeoutException;
@@ -184,11 +183,6 @@ public class TimeoutTaskMonitor implements TaskMonitor {
return delegate.getProgress();
}
- @Override
- public void reportIssue(Issue issue) {
- delegate.reportIssue(issue);
- }
-
private void timeout() {
didTimeout.set(true);
timeoutCallback.call();
@@ -225,14 +219,4 @@ public class TimeoutTaskMonitor implements TaskMonitor {
public void clearCanceled() {
delegate.clearCanceled();
}
-
- @Override
- public void addIssueListener(IssueListener listener) {
- delegate.addIssueListener(listener);
- }
-
- @Override
- public void removeIssueListener(IssueListener listener) {
- delegate.removeIssueListener(listener);
- }
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/WrappingTaskMonitor.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/WrappingTaskMonitor.java
index 71e34965ce..6e5761d9d7 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/WrappingTaskMonitor.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/WrappingTaskMonitor.java
@@ -15,7 +15,6 @@
*/
package ghidra.util.task;
-import ghidra.util.Issue;
import ghidra.util.exception.CancelledException;
/**
@@ -91,11 +90,6 @@ public class WrappingTaskMonitor implements TaskMonitor {
return delegate.getProgress();
}
- @Override
- public void reportIssue(Issue issue) {
- delegate.reportIssue(issue);
- }
-
@Override
public void cancel() {
delegate.cancel();
@@ -125,14 +119,4 @@ public class WrappingTaskMonitor implements TaskMonitor {
public void clearCanceled() {
delegate.clearCanceled();
}
-
- @Override
- public void addIssueListener(IssueListener listener) {
- delegate.addIssueListener(listener);
- }
-
- @Override
- public void removeIssueListener(IssueListener listener) {
- delegate.removeIssueListener(listener);
- }
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LockingTaskMonitor.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LockingTaskMonitor.java
index ef9e909b7a..9b2afafbbb 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LockingTaskMonitor.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LockingTaskMonitor.java
@@ -15,10 +15,7 @@
*/
package ghidra.framework.data;
-import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
-import ghidra.util.datastruct.WeakDataStructureFactory;
-import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
@@ -35,7 +32,6 @@ class LockingTaskMonitor implements TaskMonitor {
private boolean showProgressValue = true;
private String msg;
private MyTaskDialog taskDialog;
- private WeakSet
+ * Operations that support a task monitor should periodically
* check to see if the operation has been cancelled and abort. If possible, the
* operation should also provide periodic progress information. If it can estimate a
* percentage done, then it should use the
* Note: setting this value will reset the progress to be the max if the progress is currently
* greater than the new new max value.
@@ -80,88 +124,70 @@ public interface TaskMonitor {
public void setMaximum(long max);
/**
- * Returns the current maximum value for progress.
- * @return
+ * Returns the current maximum value for progress
+ * @return the maximum progress value
*/
public long getMaximum();
/**
- * An indeterminate task monitor may choose to show an animation instead of updating progress.
+ * An indeterminate task monitor may choose to show an animation instead of updating progress
+ * @param indeterminate true if indeterminate
*/
public void setIndeterminate(boolean indeterminate);
/**
- * Check to see if this monitor has been canceled.
- * @throws CancelledException if monitor has been cancelled.
+ * Check to see if this monitor has been canceled
+ * @throws CancelledException if monitor has been cancelled
*/
public void checkCanceled() throws CancelledException;
/**
- * A convenience method to increment the current progress by the given value.
- * @param incrementAmount The amount by which to increment the progress.
+ * A convenience method to increment the current progress by the given value
+ * @param incrementAmount The amount by which to increment the progress
*/
public void incrementProgress(long incrementAmount);
/**
* Returns the current progress value or {@link #NO_PROGRESS_VALUE} if there is no value
- * set.
+ * set
* @return the current progress value or {@link #NO_PROGRESS_VALUE} if there is no value
- * set.
+ * set
*/
public long getProgress();
/**
- * Notify that an issue occurred while processing.
- * @param issue the issue that was encountered
- */
- public void reportIssue(Issue issue);
-
- /**
- * Cancel the task.
+ * Cancel the task
*/
public void cancel();
/**
- * Add cancelled listener.
- * @param listener
+ * Add cancelled listener
+ * @param listener the cancel listener
*/
public void addCancelledListener(CancelledListener listener);
/**
- * Remove cancelled listener.
- * @param listener
+ * Remove cancelled listener
+ * @param listener the cancel listener
*/
public void removeCancelledListener(CancelledListener listener);
/**
- * Set the enablement of the Cancel button.
+ * Set the enablement of the Cancel button
* @param enable true means to enable the cancel button
*/
public void setCancelEnabled(boolean enable);
/**
* Returns true if cancel ability is enabled
+ * @return true if cancel ability is enabled
*/
public boolean isCancelEnabled();
/**
- * Clear the cancellation so that this TaskMonitor may be reused.
+ * Clear the cancellation so that this TaskMonitor may be reused
*
*/
public void clearCanceled();
- /**
- * Add an issue listener to this monitor.
- *
- * @param listener the listener
- */
- public void addIssueListener(IssueListener listener);
-
- /**
- * Removes an issue listener to this monitor.
- *
- * @param listener the listener
- */
- public void removeIssueListener(IssueListener listener);
-
}
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitorService.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitorService.java
new file mode 100644
index 0000000000..efdb06ea84
--- /dev/null
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitorService.java
@@ -0,0 +1,154 @@
+/* ###
+ * 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.task;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.swing.SwingUtilities;
+
+/**
+ * Provides access to the {@link TaskMonitor} instance for the current thread. The first
+ * time a monitor is requested via {@link #getMonitor()}, a "primary" monitor (one
+ * that allows updating of task progress and status messages) is returned; all
+ * subsequent requests will return a "secondary" monitor, which only allows
+ * status message updates. This is to keep the progress bar from being updated
+ * simultaneously by multiple parties.
+ *
+ * Note: {@link TaskMonitor monitor} instances are registered with this service via the
+ * {@link #register(TaskMonitor) setMonitor} call, and will be available to that thread until
+ * the {@link #remove(int) remove} method is called.
+ *
+ * Note: Because monitor instances are managed by a {@link ThreadLocal} object, they will be
+ * cleaned up automatically by the GC when the thread is terminated.
+ */
+public class TaskMonitorService {
+
+ /**
+ * The {@link TaskMonitor} instance. ThreadLocal ensures that each thread has access
+ * to its own monitor.
+ */
+ private static ThreadLocal
+ * Note: This should generally not need to be called as the GC will clean up thread local
+ * objects when the associated thread is finished.
+ *
+ * @param monitorId the unique ID for the monitor to be removed
+ */
+ public static void remove(int monitorId) {
+
+ if (monitorId != localMonitorId.get()) {
+ throw new IllegalArgumentException("Invalid monitor id for this thread: " + monitorId);
+ }
+
+ localMonitor.remove();
+ }
+
+ /**
+ * Hide the constructor - this should not be instantiated
+ */
+ private TaskMonitorService() {
+ // nothing to do
+ }
+}
indeterminate
property of the progress bar,
* which determines whether the progress bar is in determinate
@@ -201,17 +211,6 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
});
}
- /**
- * Returns true if {@link #setIndeterminate(boolean)} with a value of true has
- * been called.
- *
- * @return true if {@link #setIndeterminate(boolean)} with a value of true has
- * been called.
- */
- public boolean isIndeterminate() {
- return isIndeterminate.get();
- }
-
@Override
public synchronized void setCancelEnabled(boolean enable) {
if (cancelEnabled != enable) {
@@ -243,67 +242,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
isCancelled = false;
}
}
-
- /**
- * Set whether the progress bar should be visible.
- * @param b true if the progress bar should be visible
- */
- public synchronized void showProgress(boolean b) {
- if (b != showingProgress) {
- showingProgress = b;
- SystemUtilities.runSwingLater(updateProgressPanelRunnable);
- }
- }
-
- /**
- * Set the name of the task; the name shows up in the tool tip for
- * the cancel button.
- * @param name the name of the task
- */
- public void setTaskName(String name) {
- taskName = name;
- SystemUtilities.runSwingLater(updateToolTipRunnable);
- }
-
- /**
- * Show or not show the cancel button according to the showCancel param.
- */
- public void showCancelButton(boolean showCancel) {
-
- if (showCancel == showingCancelButton) {
- return;
- }
-
- if (showCancel) {
- add(eastButtonPanel, BorderLayout.EAST);
- }
- else {
- remove(eastButtonPanel);
- }
- showingCancelButton = showCancel;
- }
-
- /**
- * Show or not show the progress icon (spinning globe) according to
- * the showIcon param.
- */
- public void showProgressIcon(final boolean showIcon) {
- if (showIcon == showingIcon) {
- return;
- }
- Runnable r = () -> {
- if (showIcon) {
- activeProgressPanel.add(imageLabel, BorderLayout.EAST);
- }
- else {
- activeProgressPanel.remove(imageLabel);
- }
- showingIcon = showIcon;
- };
-
- SystemUtilities.runSwingNow(r);
- }
-
+
@Override
public void setShowProgressValue(boolean showProgressValue) {
this.paintProgressValue = showProgressValue;
@@ -312,19 +251,121 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override
public long getMaximum() {
- return max;
+ return maxProgress;
+ }
+
+ /**
+ * Reset this monitor so that it can be reused
+ */
+ @Override
+ public synchronized void reset() {
+ isCancelled = false;
+ taskID.incrementAndGet();
+ }
+
+ /**
+ * Returns true if {@link #setIndeterminate(boolean)} with a value of true has
+ * been called.
+ *
+ * @return true if {@link #setIndeterminate(boolean)} with a value of true has
+ * been called.
+ */
+ public boolean isIndeterminate() {
+ return isIndeterminate.get();
+ }
+
+ /**
+ * Set whether the progress bar should be visible
+ *
+ * @param show true if the progress bar should be visible
+ */
+ public synchronized void showProgress(boolean show) {
+ if (show != showingProgress) {
+ showingProgress = show;
+ SystemUtilities.runSwingLater(updateProgressPanelRunnable);
+ }
+ }
+
+ /**
+ * Set the name of the task; the name shows up in the tool tip for
+ * the cancel button.
+ *
+ * @param name the name of the task
+ */
+ public void setTaskName(String name) {
+ taskName = name;
+ SystemUtilities.runSwingLater(updateToolTipRunnable);
+ }
+
+ /**
+ * Set the visibility of the cancel button
+ *
+ * @param visible if true, show the cancel button; false otherwise
+ */
+ public void setCancelButtonVisibility(boolean visible) {
+
+ if (visible == showingCancelButton) {
+ return;
+ }
+
+ if (visible) {
+ add(cancelPanel, BorderLayout.EAST);
+ }
+ else {
+ remove(cancelPanel);
+ }
+
+ repaint();
+ showingCancelButton = visible;
+ }
+
+ /**
+ * Sets the visibility of the progress icon
+ *
+ * @param visible if true, display the progress icon
+ */
+ public void showProgressIcon(boolean visible) {
+ if (visible == showingIcon) {
+ return;
+ }
+ Runnable r = () -> {
+ if (visible) {
+ mainContentPanel.add(progressPanel, BorderLayout.EAST);
+ }
+ else {
+ mainContentPanel.remove(progressPanel);
+ }
+ showingIcon = visible;
+ };
+
+ SystemUtilities.runSwingNow(r);
+ }
+
+ protected void notifyChangeListeners() {
+ Runnable r = () -> {
+ for (CancelledListener mcl : listeners) {
+ mcl.cancelled();
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ private synchronized void startUpdateTimer() {
+ if (!updateTimer.isRunning()) {
+ updateTimer.start();
+ }
}
private synchronized void update() {
- if (message != null) {
- messageLabel.setText(message);
- message = null;
+ if (progressMessage != null) {
+ messageLabel.setText(progressMessage);
+ progressMessage = null;
}
- if (max != lastMax) {
- setMaxValueInProgressBar(max);
- lastMax = max;
+ if (maxProgress != lastMaxProgress) {
+ setMaxValueInProgressBar(maxProgress);
+ lastMaxProgress = maxProgress;
}
if (progress != lastProgress) {
@@ -379,10 +420,10 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private synchronized void updateProgressPanel() {
if (showingProgress) {
- progressPanel.add(progressBar, BorderLayout.NORTH);
+ progressBarPanel.add(progressBar, BorderLayout.NORTH);
}
else {
- progressPanel.remove(progressBar);
+ progressBarPanel.remove(progressBar);
}
}
@@ -424,24 +465,24 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
progressBar.setStringPainted(true);
ToolTipManager.sharedInstance().registerComponent(progressBar);
- createAnimatedIcon();
+ progressPanel = new HourglassAnimationPanel();
- progressPanel = new JPanel(new VerticalLayout(0));
- progressPanel.add(progressBar);
+ progressBarPanel = new JPanel(new VerticalLayout(0));
+ progressBarPanel.add(progressBar);
if (includeTextField) {
- progressPanel.add(messageLabel);
+ progressBarPanel.add(messageLabel);
progressBar.setPreferredSize(new Dimension(180, 12));
}
else {
progressBar.setBorderPainted(true);
Dimension size = progressBar.getPreferredSize();
- progressPanel.setBorder(BorderFactory.createEmptyBorder(
- (imageLabel.getPreferredSize().height - size.height) / 2, 0, 0, 8));
+ progressBarPanel.setBorder(BorderFactory.createEmptyBorder(
+ (progressPanel.getPreferredSize().height - size.height) / 2, 0, 0, 8));
}
- activeProgressPanel = new JPanel(new BorderLayout());
- activeProgressPanel.add(progressPanel, BorderLayout.CENTER);
- activeProgressPanel.add(imageLabel, BorderLayout.EAST);
+ mainContentPanel = new JPanel(new BorderLayout());
+ mainContentPanel.add(progressBarPanel, BorderLayout.CENTER);
+ mainContentPanel.add(progressPanel, BorderLayout.EAST);
ImageIcon icon = Icons.STOP_ICON;
cancelButton = new EmptyBorderButton(icon);
@@ -452,86 +493,15 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
cancelButton.setFocusable(false);
cancelButton.setRolloverEnabled(true);
- add(activeProgressPanel, BorderLayout.CENTER);
+ add(mainContentPanel, BorderLayout.CENTER);
if (includeCancelButton) {
- eastButtonPanel = new JPanel();
- eastButtonPanel.setLayout(new BoxLayout(eastButtonPanel, BoxLayout.Y_AXIS));
- eastButtonPanel.add(Box.createVerticalGlue());
- eastButtonPanel.add(cancelButton);
- eastButtonPanel.add(Box.createVerticalGlue());
- add(eastButtonPanel, BorderLayout.EAST);
- }
- }
-
- private void createAnimatedIcon() {
- String[] filenames = { "images/hourglass24_01.png", "images/hourglass24_02.png",
- "images/hourglass24_02.png", "images/hourglass24_03.png", "images/hourglass24_03.png",
- "images/hourglass24_04.png", "images/hourglass24_04.png", "images/hourglass24_05.png",
- "images/hourglass24_05.png", "images/hourglass24_06.png", "images/hourglass24_06.png",
- "images/hourglass24_07.png", "images/hourglass24_07.png", "images/hourglass24_08.png",
- "images/hourglass24_08.png", "images/hourglass24_09.png", "images/hourglass24_10.png",
- "images/hourglass24_11.png" };
- imageLabel =
- new GIconLabel(new AnimatedIcon(ResourceManager.loadImages(filenames), 150, 0));
- }
-
- @Override
- public void incrementProgress(long incrementAmount) {
- setProgress(progress + incrementAmount);
- }
-
- @Override
- public long getProgress() {
- return progress;
- }
-
- protected void notifyChangeListeners() {
- Runnable r = () -> {
- synchronized (listeners) {
- for (CancelledListener mcl : listeners) {
- mcl.cancelled();
- }
- }
- };
- SwingUtilities.invokeLater(r);
- }
-
- @Override
- public void addCancelledListener(CancelledListener mcl) {
- synchronized (listeners) {
- listeners.add(mcl);
- }
- }
-
- @Override
- public void removeCancelledListener(CancelledListener mcl) {
- synchronized (listeners) {
- listeners.remove(mcl);
- }
- }
-
- @Override
- public void addIssueListener(IssueListener listener) {
- if (issueListeners == null) {
- issueListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
- }
- }
-
- @Override
- public void removeIssueListener(IssueListener listener) {
- if (issueListeners != null) {
- issueListeners.remove(listener);
- }
-
- }
-
- @Override
- public void reportIssue(Issue issue) {
- if (issueListeners != null) {
- for (IssueListener listener : issueListeners) {
- listener.issueReported(issue);
- }
+ cancelPanel = new JPanel();
+ cancelPanel.setLayout(new BoxLayout(cancelPanel, BoxLayout.Y_AXIS));
+ cancelPanel.add(Box.createVerticalGlue());
+ cancelPanel.add(cancelButton);
+ cancelPanel.add(Box.createVerticalGlue());
+ add(cancelPanel, BorderLayout.EAST);
}
}
}
diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java
index 08df0dc777..bc1ce42fd1 100644
--- a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java
+++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java
@@ -28,6 +28,7 @@ import org.junit.After;
import org.junit.Test;
import docking.test.AbstractDockingTest;
+import ghidra.util.exception.CancelledException;
public class TaskDialogTest extends AbstractDockingTest {
@@ -94,6 +95,89 @@ public class TaskDialogTest extends AbstractDockingTest {
assertTrue(dialogSpy.wasShown());
assertSwingThreadFinishedBeforeTask();
}
+
+ /*
+ * Verifies that if the dialog cancel button is activated, the task is cancelled
+ */
+ @Test
+ public void testTaskCancel() throws Exception {
+ SlowModalTask task = new SlowModalTask();
+ TaskDialogSpy dialogSpy = launchTask(task);
+
+ dialogSpy.doShow();
+
+ waitForTask();
+
+ assertFalse(dialogSpy.isCancelled());
+ dialogSpy.cancel();
+ assertTrue(dialogSpy.isCancelled());
+ }
+
+ /*
+ * Verifies that if the task does not allow cancellation, the cancel button on the GUI
+ * is disabled
+ */
+ @Test
+ public void testTaskNoCancel() throws Exception {
+ SlowModalTask task = new SlowModalTask();
+ TaskDialogSpy dialogSpy = launchTask(task);
+
+ dialogSpy.doShow();
+ dialogSpy.setCancelEnabled(false);
+
+ waitForTask();
+
+ assertFalse(dialogSpy.isCancelEnabled());
+ }
+
+ /*
+ * Verifies that the progress value can be successfully updated
+ * after using the {@link TaskMonitorService} to retrieve a monitor.
+ */
+ @Test
+ public void testUpdateProgressSuccess() throws Exception {
+
+ TaskLauncher.launch(new Task("task") {
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+ TaskMonitor monitor1 = TaskMonitorService.getMonitor();
+ long val = monitor1.getProgress();
+
+ monitor1.setProgress(10);
+ val = monitor1.getProgress();
+ assertEquals(val, 10);
+ }
+ });
+ }
+
+ /*
+ * Verifies that the progress value will NOT be updated if the caller is a
+ * secondary monitor. As a bonus, this also verifies that the Task Launcher does
+ * not lock the task for future progress updates when a new task is launched.
+ */
+ @Test
+ public void testUpdatePogressFail() throws Exception {
+
+ TaskLauncher.launch(new Task("task") {
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+
+ TaskMonitor monitor1 = TaskMonitorService.getMonitor();
+ TaskMonitor monitor2 = TaskMonitorService.getMonitor();
+
+ // Update should be accepted
+ monitor1.setProgress(10);
+
+ // Update should fail
+ monitor2.setProgress(20);
+
+ long val = monitor2.getProgress();
+ assertEquals(val, 10);
+ }
+ });
+
+ waitForTasks();
+ }
private void assertSwingThreadBlockedForTask() {
TDEvent lastEvent = eventQueue.peekLast();
@@ -160,7 +244,7 @@ public class TaskDialogTest extends AbstractDockingTest {
public TaskDialogSpy(Task task) {
super(task);
}
-
+
@Override
protected void doShow() {
shown.set(true);
diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/FutureTaskMonitor.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/FutureTaskMonitor.java
index ee941222f3..143da1390d 100644
--- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/FutureTaskMonitor.java
+++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/FutureTaskMonitor.java
@@ -18,9 +18,9 @@ package generic.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
-import ghidra.util.Issue;
import ghidra.util.exception.CancelledException;
-import ghidra.util.task.*;
+import ghidra.util.task.CancelledListener;
+import ghidra.util.task.TaskMonitor;
/**
* This is the FutureTask that will be used to call the {@link QCallback} to work on
@@ -131,11 +131,6 @@ class FutureTaskMonitor extends FutureTaskRunnable
is used to create a thread,
* starting the thread causes the object's run
method to be called in that
- * separately executing thread
+ * separately executing thread.
+ * TaskMonitor
provides an interface by means of which a
* potentially long running task can show its progress and also check if the user
- * has cancelled the operation. Operations that support a task monitor should periodically
+ * has cancelled the operation.
+ * setProgress(int)
method,
- * otherwise it
- * should just call the setMessage(String)
method.
+ * otherwise it should just call the setMessage(String)
method.
*/
public interface TaskMonitor {
public static final TaskMonitor DUMMY = new StubTaskMonitor();
- /**
- * A value to indicate that this monitor has no progress value set.
- */
+ /** A value to indicate that this monitor has no progress value set */
public static final int NO_PROGRESS_VALUE = -1;
/**
- * Returns true if the user has cancelled the operation.
+ * Returns true if the user has cancelled the operation
+ *
+ * @return true if the user has cancelled the operation
*/
public boolean isCancelled();
/**
- * True (the default) signals to paint the progress information inside of the progress bar.
+ * Returns true if the monitor has been initialized
+ *
+ * @return true if the monitor has been initialized
+ */
+ public default boolean isInitialized() {
+ return false;
+ }
+
+ /**
+ * Sets the initialization state of the monitor
+ *
+ * @param init true for initialized, false otherwise
+ */
+ public default void setInitialized(boolean init) {
+ // do nothing - this is defaulted for backward compatibility so current
+ // task monitor implementations do not have to change
+ }
+
+ /**
+ * Restores the monitor to an uninitialized state. This will result in the primary
+ * monitor being returned from the {@link TaskMonitorService} on the next
+ * invocation.
+ */
+ public default void reset() {
+ synchronized (this) {
+ setMessage("");
+ setProgress(0);
+ setMaximum(0);
+ setInitialized(false);
+ clearCanceled();
+ }
+ }
+
+ /**
+ * True (the default) signals to paint the progress information inside of the progress bar
*
* @param showProgressValue true to paint the progress value; false to not
*/
public void setShowProgressValue(boolean showProgressValue);
/**
- * Sets a message giving additional information about the current
- * progress.
- * @param message more information
+ * Sets the message displayed on the task monitor
+ *
+ * @param message the message to display
*/
public void setMessage(String message);
+
+ /**
+ * Returns a version of this monitor that cannot have its progress state changed. This is
+ * meant for sub-tasks that should not be allowed to hijack task progress.
+ *
+ * @return null
+ */
+ public default TaskMonitor getSecondaryMonitor() {
+ return null;
+ }
/**
- * Sets the current progress value.
+ * Sets the current progress value
* @param value progress value
*/
public void setProgress(long value);
@@ -71,7 +115,7 @@ public interface TaskMonitor {
public void initialize(long max);
/**
- * Set the progress maximum value.
+ * Set the progress maximum value
*