diff --git a/src/main/org/insa/graphics/BlockingActionFactory.java b/src/main/org/insa/graphics/BlockingActionFactory.java new file mode 100644 index 0000000..fda0203 --- /dev/null +++ b/src/main/org/insa/graphics/BlockingActionFactory.java @@ -0,0 +1,58 @@ +package org.insa.graphics; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.JOptionPane; + +public class BlockingActionFactory { + + // List of running actions. + private ArrayList actions = new ArrayList<>(); + + // Parent component. + private Component parentComponent; + + public BlockingActionFactory(Component parentComponent) { + this.parentComponent = parentComponent; + } + + public void addAction(RunningAction action) { + actions.add(action); + } + + public ActionListener createBlockingAction(ActionListener listener) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + boolean accepted = true; + + // Check if actions... + for (int i = 0; i < actions.size() && accepted; ++i) { + RunningAction action = actions.get(i); + // If action is running, ask user... + if (action.isRunning()) { + System.out.println("Action " + action.getInformation() + " is running... "); + if (JOptionPane.showConfirmDialog(parentComponent, "Action {" + action.getInformation() + + "} is running, do you want to stop it?") == JOptionPane.OK_OPTION) { + System.out.println("Action " + action.getInformation() + " has been interrupted."); + action.interrupt(); + } + else { + System.out.println("Action " + action.getInformation() + " not interrupted... "); + accepted = false; + } + } + } + + // If action is accepted, run it... + if (accepted) { + listener.actionPerformed(e); + } + } + }; + } + +} diff --git a/src/main/org/insa/graphics/BlockingActionListener.java b/src/main/org/insa/graphics/BlockingActionListener.java deleted file mode 100644 index 8539d78..0000000 --- a/src/main/org/insa/graphics/BlockingActionListener.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.insa.graphics; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -public abstract class BlockingActionListener implements ActionListener { - - @Override - public void actionPerformed(ActionEvent e) { - this.actionAccepted(e); - } - - public abstract void actionAccepted(ActionEvent e); -} diff --git a/src/main/org/insa/graphics/MainWindow.java b/src/main/org/insa/graphics/MainWindow.java index 72fbab2..9dd5da9 100644 --- a/src/main/org/insa/graphics/MainWindow.java +++ b/src/main/org/insa/graphics/MainWindow.java @@ -12,10 +12,7 @@ import java.awt.event.WindowEvent; import java.io.DataInputStream; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.io.PrintStream; -import java.time.Duration; -import java.time.Instant; import java.util.ArrayList; import javax.swing.BorderFactory; @@ -44,17 +41,19 @@ import org.insa.algo.shortestpath.ShortestPathData.Mode; import org.insa.algo.shortestpath.ShortestPathGraphicObserver; import org.insa.algo.shortestpath.ShortestPathSolution; import org.insa.algo.weakconnectivity.WeaklyConnectedComponentGraphicObserver; +import org.insa.algo.weakconnectivity.WeaklyConnectedComponentTextObserver; import org.insa.algo.weakconnectivity.WeaklyConnectedComponentsAlgorithm; import org.insa.algo.weakconnectivity.WeaklyConnectedComponentsData; import org.insa.graph.Graph; import org.insa.graph.Node; import org.insa.graph.Path; -import org.insa.graph.io.GraphReader; import org.insa.graph.io.BinaryGraphReader; import org.insa.graph.io.BinaryGraphReaderV2; import org.insa.graph.io.BinaryPathReader; +import org.insa.graph.io.GraphReader; import org.insa.graph.io.MapMismatchException; import org.insa.graph.io.Openfile; +import org.insa.graphics.MultiPointsClickListener.CallableWithNodes; import org.insa.graphics.drawing.BasicDrawing; import org.insa.graphics.drawing.BlackAndWhiteGraphPalette; import org.insa.graphics.drawing.Drawing; @@ -62,30 +61,6 @@ import org.insa.graphics.drawing.MapViewDrawing; public class MainWindow extends JFrame { - protected class JOutputStream extends OutputStream { - private JTextArea textArea; - - public JOutputStream(JTextArea textArea) { - this.textArea = textArea; - } - - @Override - public void write(int b) throws IOException { - // redirects data to the text area - textArea.setText(textArea.getText() + String.valueOf((char) b)); - // scrolls the text area to the end of data - textArea.setCaretPosition(textArea.getDocument().getLength()); - // keeps the textArea up to date - textArea.update(textArea.getGraphics()); - } - } - - protected interface CallableWithNodes { - - void call(ArrayList nodes); - - }; - /** * */ @@ -102,14 +77,13 @@ public class MainWindow extends JFrame { private static final int THREAD_TIMER_DELAY = 1000; // in milliseconds // Current graph. - private Graph graph; + protected Graph graph; // Current loaded path. - private Path currentPath; + // private Path currentPath; // Drawing and click adapter. - private Drawing drawing; - private MultiPointsClickListener clickAdapter = null; + protected Drawing drawing; // Main panel. private JSplitPane mainPanel; @@ -124,23 +98,41 @@ public class MainWindow extends JFrame { private JLabel mapIdPanel; // Thread information - private Instant threadStartTime; private Timer threadTimer; private JPanel threadPanel; // Log stream and print stream - private JOutputStream logStream; + private StreamCapturer logStream; @SuppressWarnings("unused") private PrintStream printStream; // Current running thread - private Thread currentThread; + private ThreadWrapper currentThread; + + // Multi point listener + private MultiPointsClickListener clickAdapter = null; + + // Factory + private BlockingActionFactory baf; public MainWindow() { super(WINDOW_TITLE); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); setLayout(new BorderLayout()); + + // Create drawing and action listeners... + this.drawing = new BasicDrawing(); + + this.clickAdapter = new MultiPointsClickListener(this); + this.currentThread = new ThreadWrapper(this); + this.baf = new BlockingActionFactory(this); + this.baf.addAction(clickAdapter); + this.baf.addAction(currentThread); + + // Click adapter + addDrawingClickListeners(); setJMenuBar(createMenuBar()); addWindowListener(new WindowAdapter() { @@ -158,21 +150,15 @@ public class MainWindow extends JFrame { // Create graph area mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); - this.drawing = new BasicDrawing(); - - // Click adapter - addDrawingClickListeners(); - JTextArea infoPanel = new JTextArea(); infoPanel.setMinimumSize(new Dimension(200, 50)); infoPanel.setBackground(Color.WHITE); infoPanel.setLineWrap(true); infoPanel.setEditable(false); - this.logStream = new JOutputStream(infoPanel); + this.logStream = new StreamCapturer(infoPanel); this.printStream = new PrintStream(this.logStream); mainPanel.setResizeWeight(0.8); - // sp.setEnabled(false); mainPanel.setDividerSize(5); mainPanel.setBackground(Color.WHITE); @@ -183,10 +169,10 @@ public class MainWindow extends JFrame { // Top Panel this.add(createTopPanel(), BorderLayout.NORTH); this.add(createStatusBar(), BorderLayout.SOUTH); + } private void restartThreadTimer() { - threadStartTime = Instant.now(); threadTimer.restart(); } @@ -200,7 +186,7 @@ public class MainWindow extends JFrame { */ private void launchThread(Runnable runnable, boolean canInterrupt) { if (canInterrupt) { - currentThread = new Thread(new Runnable() { + currentThread.setThread(new Thread(new Runnable() { @Override public void run() { restartThreadTimer(); @@ -208,22 +194,22 @@ public class MainWindow extends JFrame { runnable.run(); clearCurrentThread(); } - }); + })); } else { - currentThread = new Thread(runnable); + currentThread.setThread(new Thread(runnable)); } - currentThread.start(); + currentThread.startThread(); } private void launchThread(Runnable runnable) { launchThread(runnable, true); } - private void clearCurrentThread() { + protected void clearCurrentThread() { stopThreadTimer(); threadPanel.setVisible(false); - currentThread = null; + currentThread.setThread(null); } private void launchShortestPathThread(ShortestPathAlgorithm spAlgorithm) { @@ -241,7 +227,6 @@ public class MainWindow extends JFrame { } private void addDrawingClickListeners() { - this.clickAdapter = new MultiPointsClickListener(graph, drawing); drawing.addDrawingClickListener(this.clickAdapter); } @@ -265,9 +250,9 @@ public class MainWindow extends JFrame { // Open Map item... openMapItem = new JMenuItem("Open Map... ", KeyEvent.VK_O); openMapItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK)); - openMapItem.addActionListener(new BlockingActionListener() { + openMapItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter("Map & compressed map files", "map", "map2", "mapgr", "map.gz"); @@ -313,14 +298,14 @@ public class MainWindow extends JFrame { }, false); } } - }); + })); // Open Path item... JMenuItem openPathItem = new JMenuItem("Open Path... ", KeyEvent.VK_P); openPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK)); - openPathItem.addActionListener(new BlockingActionListener() { + openPathItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter("Path & compressed path files", "path", "path.gz"); @@ -336,7 +321,8 @@ public class MainWindow extends JFrame { return; } try { - currentPath = reader.readPath(graph); + Path path = reader.readPath(graph); + drawing.drawPath(path); } catch (MapMismatchException exception) { JOptionPane.showMessageDialog(MainWindow.this, @@ -347,18 +333,17 @@ public class MainWindow extends JFrame { JOptionPane.showMessageDialog(MainWindow.this, "Unable to read path from the selected file."); return; } - drawing.drawPath(currentPath); } } - }); + })); graphLockItems.add(openPathItem); // Close item JMenuItem closeItem = new JMenuItem("Quit", KeyEvent.VK_Q); closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK)); - closeItem.addActionListener(new BlockingActionListener() { + closeItem.addActionListener(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { MainWindow.this.dispatchEvent(new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING)); } }); @@ -373,9 +358,9 @@ public class MainWindow extends JFrame { // Second menu JMenuItem drawGraphItem = new JMenuItem("Redraw", KeyEvent.VK_R); drawGraphItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK)); - drawGraphItem.addActionListener(new BlockingActionListener() { + drawGraphItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { launchThread(new Runnable() { @Override public void run() { @@ -384,13 +369,13 @@ public class MainWindow extends JFrame { } }); } - }); + })); graphLockItems.add(drawGraphItem); JMenuItem drawGraphBWItem = new JMenuItem("Redraw (B&W)", KeyEvent.VK_B); drawGraphBWItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.ALT_MASK)); - drawGraphBWItem.addActionListener(new BlockingActionListener() { + drawGraphBWItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { launchThread(new Runnable() { @Override public void run() { @@ -399,13 +384,13 @@ public class MainWindow extends JFrame { } }); } - }); + })); graphLockItems.add(drawGraphBWItem); JMenuItem drawGraphMapsforgeItem = new JMenuItem("Redraw (Map)", KeyEvent.VK_M); drawGraphMapsforgeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK)); - drawGraphMapsforgeItem.addActionListener(new BlockingActionListener() { + drawGraphMapsforgeItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { launchThread(new Runnable() { @Override public void run() { @@ -414,7 +399,7 @@ public class MainWindow extends JFrame { } }); } - }); + })); graphLockItems.add(drawGraphMapsforgeItem); JMenu graphMenu = new JMenu("Graph"); @@ -428,13 +413,13 @@ public class MainWindow extends JFrame { // Weakly connected components JMenuItem wccItem = new JMenuItem("Weakly Connected Components"); - wccItem.addActionListener(new BlockingActionListener() { + wccItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { WeaklyConnectedComponentsData instance = new WeaklyConnectedComponentsData(graph); WeaklyConnectedComponentsAlgorithm algo = new WeaklyConnectedComponentsAlgorithm(instance); algo.addObserver(new WeaklyConnectedComponentGraphicObserver(drawing)); - // algo.addObserver(new WeaklyConnectedComponentTextObserver(printStream)); + algo.addObserver(new WeaklyConnectedComponentTextObserver(printStream)); launchThread(new Runnable() { @Override public void run() { @@ -442,13 +427,13 @@ public class MainWindow extends JFrame { } }); } - }); + })); // Shortest path JMenuItem bellmanItem = new JMenuItem("Shortest Path (Bellman-Ford)"); - bellmanItem.addActionListener(new BlockingActionListener() { + bellmanItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override - public void actionAccepted(ActionEvent e) { + public void actionPerformed(ActionEvent e) { int idx = JOptionPane.showOptionDialog(MainWindow.this, "Which mode do you want?", "Mode selection", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, Mode.values(), Mode.LENGTH); @@ -463,7 +448,7 @@ public class MainWindow extends JFrame { }); } } - }); + })); graphLockItems.add(wccItem); graphLockItems.add(bellmanItem); @@ -487,14 +472,6 @@ public class MainWindow extends JFrame { return menuBar; } - @SuppressWarnings("deprecation") - private void stopCurrentThread() { - // Should not be used in production code, but here I have no idea how - // to do this properly... Cannot use .interrupt() because it would requires - // the algorithm to watch the ThreadInteruption exception. - currentThread.stop(); - } - private JPanel createStatusBar() { // create the status bar panel and shove it down the bottom of the frame JPanel statusPanel = new JPanel(); @@ -513,14 +490,12 @@ public class MainWindow extends JFrame { @Override public void actionPerformed(ActionEvent e) { - if (currentThread != null && currentThread.isAlive()) { + if (currentThread.isRunning()) { int confirmed = JOptionPane.showConfirmDialog(null, "Are you sure you want to kill the running thread?", "Kill Confirmation", JOptionPane.YES_NO_OPTION); if (confirmed == JOptionPane.YES_OPTION) { - stopCurrentThread(); - clearCurrentThread(); - threadPanel.setVisible(false); + currentThread.interrupt(); } } } @@ -529,8 +504,7 @@ public class MainWindow extends JFrame { threadTimer = new Timer(THREAD_TIMER_DELAY, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - Duration elapsed = Duration.between(threadStartTime, Instant.now()); - long seconds = elapsed.getSeconds(); + long seconds = currentThread.getDuration().getSeconds(); threadTimerLabel .setText(String.format("%02d:%02d:%02d", seconds / 3600, seconds / 60 % 60, seconds % 60)); } diff --git a/src/main/org/insa/graphics/MultiPointsClickListener.java b/src/main/org/insa/graphics/MultiPointsClickListener.java index 7656768..da2965d 100644 --- a/src/main/org/insa/graphics/MultiPointsClickListener.java +++ b/src/main/org/insa/graphics/MultiPointsClickListener.java @@ -1,16 +1,26 @@ package org.insa.graphics; import java.awt.Color; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; -import org.insa.graph.Graph; import org.insa.graph.Node; import org.insa.graph.Point; -import org.insa.graphics.MainWindow.CallableWithNodes; -import org.insa.graphics.drawing.Drawing; import org.insa.graphics.drawing.DrawingClickListener; -public class MultiPointsClickListener implements DrawingClickListener { +public class MultiPointsClickListener implements DrawingClickListener, RunningAction { + + protected interface CallableWithNodes { + + /** + * Function called when the given number of nodes is reached. + * + * @param nodes + */ + void call(ArrayList nodes); + + }; // Enable/Disable. private boolean enabled = false; @@ -18,6 +28,9 @@ public class MultiPointsClickListener implements DrawingClickListener { // List of points. private ArrayList points = new ArrayList(); + // Starting time + private Instant startTime; + // Number of points to find before running. private int nTargetPoints = 0; @@ -25,14 +38,10 @@ public class MultiPointsClickListener implements DrawingClickListener { CallableWithNodes callable = null; // Graph - private final Graph graph; + private final MainWindow mainWindow; - // Drawing - private final Drawing drawing; - - public MultiPointsClickListener(Graph graph, Drawing drawing) { - this.graph = graph; - this.drawing = drawing; + public MultiPointsClickListener(MainWindow mainWindow) { + this.mainWindow = mainWindow; } /** @@ -45,14 +54,14 @@ public class MultiPointsClickListener implements DrawingClickListener { /** * Enable this listener. * - * @param nTargetPoints - * Number of point to found before calling the callable. + * @param nTargetPoints Number of point to found before calling the callable. */ public void enable(int nTargetPoints, CallableWithNodes callable) { this.enabled = true; this.nTargetPoints = nTargetPoints; this.points.clear(); this.callable = callable; + this.startTime = Instant.now(); } /** @@ -67,8 +76,8 @@ public class MultiPointsClickListener implements DrawingClickListener { if (!isEnabled()) { return; } - Node node = graph.findClosestNode(lonlat); - drawing.drawMarker(node.getPoint(), Color.BLUE); + Node node = mainWindow.graph.findClosestNode(lonlat); + mainWindow.drawing.drawMarker(node.getPoint(), Color.BLUE); points.add(node); if (points.size() == nTargetPoints) { callable.call(points); @@ -76,4 +85,29 @@ public class MultiPointsClickListener implements DrawingClickListener { } } + @Override + public boolean isRunning() { + return isEnabled(); + } + + @Override + public void interrupt() { + disable(); + } + + @Override + public Instant getStartingTime() { + return startTime; + } + + @Override + public Duration getDuration() { + return Duration.between(getStartingTime(), Instant.now()); + } + + @Override + public String getInformation() { + return getClass().getName(); + } + } diff --git a/src/main/org/insa/graphics/RunningAction.java b/src/main/org/insa/graphics/RunningAction.java new file mode 100644 index 0000000..a19aa1a --- /dev/null +++ b/src/main/org/insa/graphics/RunningAction.java @@ -0,0 +1,33 @@ +package org.insa.graphics; + +import java.time.Duration; +import java.time.Instant; + +public interface RunningAction { + + /** + * @return true if this action is running. + */ + public boolean isRunning(); + + /** + * Interrupt this action. + */ + public void interrupt(); + + /** + * @return Starting time of this action. + */ + public Instant getStartingTime(); + + /** + * @return Current duration of this action. + */ + public Duration getDuration(); + + /** + * @return Information for this action. + */ + public String getInformation(); + +} diff --git a/src/main/org/insa/graphics/StreamCapturer.java b/src/main/org/insa/graphics/StreamCapturer.java new file mode 100644 index 0000000..9dd9f26 --- /dev/null +++ b/src/main/org/insa/graphics/StreamCapturer.java @@ -0,0 +1,49 @@ +package org.insa.graphics; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.swing.JTextArea; + +public class StreamCapturer extends OutputStream { + + private StringBuilder buffer; + private String prefix = null; + private JTextArea output; + + /** + * @param prefix + * @param output + */ + public StreamCapturer(JTextArea output, String prefix) { + this.prefix = prefix; + buffer = new StringBuilder(128); + this.output = output; + } + + public StreamCapturer(JTextArea output) { + this(output, null); + } + + @Override + public void write(int b) throws IOException { + char c = (char) b; + String value = Character.toString(c); + buffer.append(value); + if (value.equals("\n")) { + output.append(getPrefix() + buffer.toString()); + output.setCaretPosition(output.getText().length()); + buffer.delete(0, buffer.length()); + } + } + + /** + * @return Formatted prefix, or empty string if no prefix is set. + */ + public String getPrefix() { + if (this.prefix == null) { + return ""; + } + return "[" + prefix + "] "; + } +} diff --git a/src/main/org/insa/graphics/ThreadWrapper.java b/src/main/org/insa/graphics/ThreadWrapper.java new file mode 100644 index 0000000..e824e22 --- /dev/null +++ b/src/main/org/insa/graphics/ThreadWrapper.java @@ -0,0 +1,62 @@ +package org.insa.graphics; + +import java.time.Duration; +import java.time.Instant; + +public class ThreadWrapper implements RunningAction { + + // Thread hold by this wrapper. + private Thread thread; + + // Starting time of the thread. + Instant startingTime; + + // MainWindow + private MainWindow mainWindow; + + public ThreadWrapper(MainWindow mainWindow) { + this.thread = null; + this.mainWindow = mainWindow; + } + + public void setThread(Thread thread) { + this.thread = thread; + } + + public void startThread() { + this.startingTime = Instant.now(); + this.thread.start(); + } + + public Thread getThread() { + return this.thread; + } + + @Override + public boolean isRunning() { + return thread != null && thread.isAlive(); + } + + @SuppressWarnings("deprecation") + @Override + public void interrupt() { + thread.stop(); + this.mainWindow.clearCurrentThread(); + } + + @Override + public Instant getStartingTime() { + return startingTime; + } + + @Override + public Duration getDuration() { + return Duration.between(getStartingTime(), Instant.now()); + } + + @Override + public String getInformation() { + return getClass().getName(); + } + +}