Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework load progress dialog #918

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions java/org/contikios/cooja/Cooja.java
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,7 @@ public static void go(Config config, List<Simulation.SimConfig> simConfigs) {
} catch (Exception e) {
logger.error("Exception when loading simulation: ", e);
}
gui.setSimulation(sim);
if (sim == null) {
autoQuit = true;
logger.error("TEST {} FAILED\n", simConfig.file());
Expand Down Expand Up @@ -1238,9 +1239,7 @@ Simulation createSimulation(Simulation.SimConfig cfg, Element root, boolean quic
? Integer.parseInt(simCfg.getChild("motedelay_us").getText())
: Integer.parseInt(cfgDelay.getText()) * Simulation.MILLISECOND;
doRemoveSimulation();
var sim = new Simulation(cfg, this, title, generatedSeed, seed, medium, delay, quick, root);
setSimulation(sim);
return sim;
return new Simulation(cfg, this, title, generatedSeed, seed, medium, delay, quick, root);
}

/**
Expand Down Expand Up @@ -1633,6 +1632,18 @@ public static void setProgressMessage(String msg, int type) {
}
}

public static void tickProgress() {
if (gui != null) {
gui.tickProgress();
}
}

public static void setMaxProgress(int max) {
if (gui != null) {
gui.setMaxProgress(max);
}
}

/**
* Load quick help for given object or identifier. Note that this method does not
* show the quick help pane.
Expand Down
116 changes: 70 additions & 46 deletions java/org/contikios/cooja/GUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
Expand All @@ -48,6 +48,7 @@
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
Expand Down Expand Up @@ -82,7 +83,6 @@
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
Expand All @@ -91,6 +91,7 @@
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ProgressMonitor;
import javax.swing.RepaintManager;
import javax.swing.SwingWorker;
import javax.swing.Timer;
Expand Down Expand Up @@ -124,7 +125,8 @@ enum LookAndFeel { CrossPlatform, FlatLaf, Nimbus, System }

static JFrame frame;
final JDesktopPane myDesktopPane;
private static JProgressBar PROGRESS_BAR;
private SwingWorker<Simulation, Object> loadWorker;
private int loadProgress;
private final ArrayList<String> PROGRESS_WARNINGS = new ArrayList<>();

final ArrayList<Class<? extends Plugin>> menuMotePluginClasses = new ArrayList<>();
Expand Down Expand Up @@ -1363,42 +1365,43 @@ private SwingWorker<Simulation, Object> createLoadSimWorker(Simulation.SimConfig
addToFileHistory(configFile);
}

final JPanel progressPanel;
final JDialog progressDialog;
ProgressMonitor progressMonitor;
if (quick) {
final String progressTitle = "Loading " + (configFile == null ? "" : configFile.getAbsolutePath());
progressDialog = new JDialog(frame, progressTitle, Dialog.ModalityType.APPLICATION_MODAL);

progressPanel = new JPanel(new BorderLayout());
var progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
progressBar.setIndeterminate(true);

PROGRESS_BAR = progressBar; // Allow various parts of Cooja to show messages.

var button = new JButton("Abort");

progressPanel.add(BorderLayout.CENTER, progressBar);
progressPanel.add(BorderLayout.SOUTH, button);
progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

progressDialog.getContentPane().add(progressPanel);
progressDialog.setSize(400, 200);

progressDialog.getRootPane().setDefaultButton(button);
progressDialog.setLocationRelativeTo(frame);
progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
final String progressTitle = "Loading " + (configFile == null ? cooja.getSimulation().getCfg().file() : configFile.getAbsolutePath());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps set a better title on the progress monitor? Otherwise the title in, for example, a Swedish system will be 'Pågår'.

Suggested change
final String progressTitle = "Loading " + (configFile == null ? cooja.getSimulation().getCfg().file() : configFile.getAbsolutePath());
final String progressTitle = "Loading " + (configFile == null ? cooja.getSimulation().getCfg().file() : configFile.getAbsolutePath());
final String defaultTitle = UIManager.getString("ProgressMonitor.progressText");
UIManager.put("ProgressMonitor.progressText", "Loading simulation");

progressMonitor = new ProgressMonitor(frame, progressTitle, "", 0, 6);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note with empty string takes no space and ProgressMonitor does not revalidate if a note is added later. Add an initial note to reserve space for notes in the dialog. Otherwise the 'Cancel' button is truncated in some UI such as Flatlaf.

Suggested change
progressMonitor = new ProgressMonitor(frame, progressTitle, "", 0, 6);
progressMonitor = new ProgressMonitor(frame, progressTitle, "Preparing to load simulation", 0, 6);

progressMonitor.setMillisToDecideToPopup(0);
progressMonitor.setMillisToPopup(0);
progressMonitor.setProgress(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore original title for ProgressMonitor if it was changed earlier.

Suggested change
progressMonitor.setProgress(0);
progressMonitor.setProgress(0);
UIManager.put("ProgressMonitor.progressText", defaultTitle);

// Emulate a modal dialog by disabling the Cooja frame. This is to avoid strange
// behaviors with Ctrl-R, possibly conflicting with the reload simulation action.
frame.setEnabled(false);
} else {
progressPanel = null;
progressDialog = null;
progressMonitor = null;
}
loadProgress = 0;

// SwingWorker can pass information from worker to process() through publish().
// Communicate information the other way through this shared queue.
final var channel = new SynchronousQueue<>(true);
var worker = new SwingWorker<Simulation, Object>() {
loadWorker = new SwingWorker<>() {
private volatile boolean cancelled;
private final PropertyChangeListener progressListener = evt -> {
if (cancelled || progressMonitor == null) return;
if (progressMonitor.isCanceled()) {
cancelled = true;
cancel(true);
return;
}
switch (evt.getPropertyName()) {
case "progress" -> progressMonitor.setProgress((Integer) evt.getNewValue());
case "maxProgress" -> progressMonitor.setMaximum((Integer) evt.getNewValue());
case "progressMessage" -> progressMonitor.setNote(evt.getNewValue().toString());
}
};

@Override
public Simulation doInBackground() {
addPropertyChangeListener(progressListener);
Element root;
try {
root = configFile == null ? cooja.extractSimulationConfig() : cooja.readSimulationConfig(cfg);
Expand Down Expand Up @@ -1440,6 +1443,19 @@ public Simulation doInBackground() {
shouldRetry = false;
PROGRESS_WARNINGS.clear();
newSim = cooja.createSimulation(config, root, quick, manualRandomSeed);
if (isCancelled() || cancelled) {
// Simulation.startPlugin can be waiting for the AWT thread at this point. Schedule
// the removal of plugins in the AWT thread too.
final var finalNewSim = newSim;
EventQueue.invokeLater(() -> {
frame.setEnabled(true);
finalNewSim.removed();
});
return null;
}
// Simulation is loaded, close progress dialog so user cannot cancel.
if (progressMonitor != null) progressMonitor.close();
cooja.setSimulation(newSim);
if (newSim != null && autoStart) {
newSim.startSimulation();
}
Expand All @@ -1450,6 +1466,7 @@ public Simulation doInBackground() {
var rv = channel.take();
if (!(rv instanceof Integer i)) return null;
shouldRetry = i == 1;
if (!shouldRetry) cancelled = true;
} catch (InterruptedException ex) {
cooja.doRemoveSimulation();
return null;
Expand Down Expand Up @@ -1505,12 +1522,16 @@ protected void process(List<Object> exs) {
}
}
} else if (ex instanceof Exception e) { // Display failure + reload button.
var retry = showErrorDialog("Simulation load error", e, true);
var retry = !isCancelled() && showErrorDialog("Simulation load error", e, true);
rv = retry ? 1 : 0;
loadProgress = 0;
if (progressMonitor != null) progressMonitor.setProgress(0);
setProgress(retry ? 0 : 100);
}
try {
channel.put(rv);
} catch (InterruptedException e) {
cancelled = true;
cancel(true);
return;
}
Expand All @@ -1519,6 +1540,9 @@ protected void process(List<Object> exs) {

@Override
protected void done() {
frame.setEnabled(true);
if (progressMonitor != null) progressMonitor.close();
if (cancelled) return;
// Simulation loaded, plugins started, now Z-order visualized plugins.
for (int z = 0; z < myDesktopPane.getAllFrames().length; z++) {
for (var plugin : myDesktopPane.getAllFrames()) {
Expand Down Expand Up @@ -1579,20 +1603,9 @@ public void actionPerformed(ActionEvent e) {
dialog.setVisible(true);
}
PROGRESS_WARNINGS.clear();
if (progressDialog != null && progressDialog.isDisplayable()) {
progressDialog.dispose();
}
}
};

if (progressDialog != null) {
java.awt.EventQueue.invokeLater(() -> {
progressPanel.setVisible(true);
progressDialog.getRootPane().getDefaultButton().addActionListener(e -> worker.cancel(true));
progressDialog.setVisible(true);
});
}
return worker;
return loadWorker;
}

public void updateProgress(boolean stoppedSimulation) {
Expand Down Expand Up @@ -1630,10 +1643,21 @@ private void updateDesktopSize() {
myDesktopPane.revalidate();
}

public void tickProgress() {
if (loadWorker != null) {
loadWorker.firePropertyChange("progress", loadProgress, ++loadProgress);
}
}

public void setMaxProgress(int max) {
if (loadWorker != null) {
loadWorker.firePropertyChange("maxProgress", 0, max);
}
}

public void setProgressMessage(String msg, int type) {
if (PROGRESS_BAR != null && PROGRESS_BAR.isShowing()) {
PROGRESS_BAR.setString(msg);
PROGRESS_BAR.setStringPainted(true);
if (loadWorker != null) {
loadWorker.firePropertyChange("progressMessage", null, msg);
}
if (type != MessageListUI.NORMAL) {
PROGRESS_WARNINGS.add(msg);
Expand Down
5 changes: 5 additions & 0 deletions java/org/contikios/cooja/Simulation.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The max progress includes any deprecated plugins. Not that it will make a noticeable difference, but should there not be a Cooja.tickProgress(); before line 360 to count skipped plugins?

Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ public Simulation(SimConfig cfg, Cooja cooja, String title, boolean generateSeed
}, "sim");
simulationThread.start();
if (root != null) {
var numMoteTypes = root.getChild("simulation").getChildren("motetype").size();
var numPlugins = root.getChildren("plugin").size();
Cooja.setMaxProgress(numMoteTypes + numPlugins);
// Track identifier of mote types to deal with the legacy-XML format that used <motetype_identifier>.
var moteTypesMap = new HashMap<String, MoteType>();
// Parse elements
Expand All @@ -295,6 +298,7 @@ public Simulation(SimConfig cfg, Cooja cooja, String title, boolean generateSeed
throw new MoteType.MoteTypeCreationException("Mote type could not be configured: " + element.getText().trim());
}
addMoteType(moteType);
Cooja.tickProgress();
for (var mote : element.getChildren("mote")) {
createMote(moteType, mote);
}
Expand Down Expand Up @@ -426,6 +430,7 @@ private SimulationCreationException startPlugin(Class<? extends Plugin> pluginCl
} catch (PluginConstructionException ex) {
return new SimulationCreationException("Failed to start plugin: " + ex.getMessage(), ex);
}
Cooja.tickProgress();
return null;
}

Expand Down