Moved setLayout into a task to fix a race condition when popping up the

cancel dialog
This commit is contained in:
ghidravore 2020-10-22 14:05:44 -04:00
parent ba80c729ec
commit 1c145dd781
4 changed files with 110 additions and 97 deletions

View File

@ -64,6 +64,7 @@ import ghidra.graph.job.GraphJobRunner;
import ghidra.service.graph.*;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
import resources.Icons;
@ -128,10 +129,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
* provides graph displays for supplied graphs
*/
private final DefaultGraphDisplayProvider graphDisplayProvider;
/**
* a 'busy' dialog to show while the layout algorithm is working
*/
private LayoutWorkingDialog layoutWorkingDialog;
/**
* the vertex that has been nominated to be 'focused' in the graph display and listing
*/
@ -359,20 +356,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onActionStateChanged((s, t) -> layoutChanged(s.getName()))
.addStates(getLayoutActionStates())
.buildAndInstallLocal(componentProvider);
// show a 'busy' dialog while the layout algorithm is computing vertex locations
viewer.getVisualizationModel()
.getLayoutModel()
.getLayoutStateChangeSupport()
.addLayoutStateChangeListener(
evt -> {
if (evt.active) {
Swing.runLater(this::showLayoutWorking);
}
else {
Swing.runLater(this::hideLayoutWorking);
}
});
}
private void createPopupActions() {
@ -569,7 +552,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
*/
private void layoutChanged(String layoutName) {
if (layoutTransitionManager != null) {
layoutTransitionManager.setLayout(layoutName);
new TaskLauncher(new SetLayoutTask(viewer, layoutTransitionManager, layoutName), null,
1000);
}
}
@ -587,27 +571,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
componentProvider.getTool().showDialog(filterDialog);
}
/**
* show the 'busy' dialog indicating that the layout algorithm is working
*/
protected void showLayoutWorking() {
if (this.layoutWorkingDialog != null) {
layoutWorkingDialog.close();
}
this.layoutWorkingDialog =
new LayoutWorkingDialog(viewer.getVisualizationModel().getLayoutAlgorithm());
componentProvider.getTool().showDialog(layoutWorkingDialog);
}
/**
* hide the 'busy' dialog for the layout algorithm work
*/
protected void hideLayoutWorking() {
if (this.layoutWorkingDialog != null) {
layoutWorkingDialog.close();
}
}
/**
* add or remove the satellite viewer
* @param context information about the event

View File

@ -1,57 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.awt.BorderLayout;
import javax.swing.*;
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import docking.DialogComponentProvider;
import ghidra.service.graph.AttributedVertex;
/**
* Extends DialogComponentProvider to make a dialog with buttons to show that the
* layout arrangement algorithm is busy
*/
public class LayoutWorkingDialog extends DialogComponentProvider {
public LayoutWorkingDialog(LayoutAlgorithm<AttributedVertex> layoutAlgorithm) {
super("Working....", false);
super.addWorkPanel(createPanel(layoutAlgorithm));
setRememberSize(false);
addDismissButton();
setDefaultButton(dismissButton);
}
/**
* Create a layout-formatted JComponent holding 2 vertical lists
* of buttons, one list for vertex filter buttons and one list for
* edge filter buttons. Each list has a border and title.
* @return a formatted JComponent (container)
*/
JComponent createPanel(LayoutAlgorithm<AttributedVertex> layoutAlgorithm) {
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
JPanel panel = new JPanel(new BorderLayout());
panel.add(progressBar, BorderLayout.CENTER);
panel.add(new JLabel("Please wait......."), BorderLayout.NORTH);
addCancelButton();
cancelButton.addActionListener(evt -> layoutAlgorithm.cancel());
return panel;
}
}

View File

@ -0,0 +1,106 @@
/* ###
* 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.graph.visualization;
import java.util.concurrent.CountDownLatch;
import org.jungrapht.visualization.VisualizationModel;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.layout.event.LayoutStateChange.*;
import org.jungrapht.visualization.layout.model.LayoutModel;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
/**
* Task to change the layout of the graph
*/
public class SetLayoutTask extends Task {
private LayoutTransitionManager layoutTransitionManager;
private String layoutName;
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
private CountDownLatch taskDone = new CountDownLatch(1);
public SetLayoutTask(VisualizationViewer<AttributedVertex, AttributedEdge> viewer,
LayoutTransitionManager layoutTransitionManager, String layoutName) {
super("Changing Graph Layout to " + layoutName, true, false, true, false);
this.viewer = viewer;
this.layoutTransitionManager = layoutTransitionManager;
this.layoutName = layoutName;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
// add a callback for when/if the user cancels the layout, use a variable cause
// monitor uses a weak listener list and it would othewise get garbage collected.
CancelledListener cancelListener = this::taskCancelled;
monitor.addCancelledListener(cancelListener);
// add a listener so we are notified when the layout starts and ends
VisualizationModel<AttributedVertex, AttributedEdge> model = viewer.getVisualizationModel();
LayoutModel<AttributedVertex> layoutModel = model.getLayoutModel();
Support support = layoutModel.getLayoutStateChangeSupport();
Listener listener = this::layoutStateChanged;
support.addLayoutStateChangeListener(listener);
// start the layout - needs to be done on swing thread to prevent issues and intermediate
// paints - should be changed in the future to not require it to be on the swing thread.
Swing.runNow(() -> layoutTransitionManager.setLayout(layoutName));
// some of the layouts are done on the calling thread and some aren't. If they are on
// the calling thread, then by now, we already got the "done" callback and the "taskDone"
// countdown latch has been triggered and won't wait. If, however, the layout has been
// diverted to another thread, we want to wait until the layout is completed
// There are two ways the latch will be triggered, the layout is completed or the user
// cancles the layout.
try {
taskDone.await();
}
catch (InterruptedException e) {
model.getLayoutAlgorithm().cancel();
}
// clean up the listeners
support.removeLayoutStateChangeListener(listener);
monitor.removeCancelledListener(cancelListener);
}
/**
* Notfication when the layout algorithm starts and stops.
* @param e the event. If the event.active is true, then the
* algorithm is starting, if false, the algorithm is done.
*/
private void layoutStateChanged(Event e) {
if (!e.active) {
// algorithm is done, release the latch
taskDone.countDown();
}
}
/**
* Callback if the user cancels the layout
*/
private void taskCancelled() {
// release the latch and tell the layout algorithm to cancel.
taskDone.countDown();
viewer.getVisualizationModel().getLayoutAlgorithm().cancel();
}
}

View File

@ -273,6 +273,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
* @return true if {@link #setIndeterminate(boolean)} with a value of <code>true</code> has
* been called.
*/
@Override
public boolean isIndeterminate() {
return isIndeterminate.get();
}