diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index 2e40b08456..7f704c9a78 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -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 diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutWorkingDialog.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutWorkingDialog.java deleted file mode 100644 index 5880caa834..0000000000 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutWorkingDialog.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/SetLayoutTask.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/SetLayoutTask.java new file mode 100644 index 0000000000..a3745d21f7 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/SetLayoutTask.java @@ -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 viewer; + private CountDownLatch taskDone = new CountDownLatch(1); + + public SetLayoutTask(VisualizationViewer 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 model = viewer.getVisualizationModel(); + LayoutModel 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(); + } + +} 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 743e150d43..9899abe20b 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 @@ -273,6 +273,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { * @return true if {@link #setIndeterminate(boolean)} with a value of true has * been called. */ + @Override public boolean isIndeterminate() { return isIndeterminate.get(); }