diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..a12b99741 --- /dev/null +++ b/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.example + Robots + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + + \ No newline at end of file diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java deleted file mode 100644 index 62e943ee1..000000000 --- a/robots/src/gui/MainApplicationFrame.java +++ /dev/null @@ -1,156 +0,0 @@ -package gui; - -import java.awt.Dimension; -import java.awt.Toolkit; -import java.awt.event.KeyEvent; - -import javax.swing.JDesktopPane; -import javax.swing.JFrame; -import javax.swing.JInternalFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -import log.Logger; - -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ -public class MainApplicationFrame extends JFrame -{ - private final JDesktopPane desktopPane = new JDesktopPane(); - - public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); - - setContentPane(desktopPane); - - - LogWindow logWindow = createLogWindow(); - addWindow(logWindow); - - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); - addWindow(gameWindow); - - setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); - } - - protected LogWindow createLogWindow() - { - LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); - logWindow.setLocation(10,10); - logWindow.setSize(300, 800); - setMinimumSize(logWindow.getSize()); - logWindow.pack(); - Logger.debug("Протокол работает"); - return logWindow; - } - - protected void addWindow(JInternalFrame frame) - { - desktopPane.add(frame); - frame.setVisible(true); - } - -// protected JMenuBar createMenuBar() { -// JMenuBar menuBar = new JMenuBar(); -// -// //Set up the lone menu. -// JMenu menu = new JMenu("Document"); -// menu.setMnemonic(KeyEvent.VK_D); -// menuBar.add(menu); -// -// //Set up the first menu item. -// JMenuItem menuItem = new JMenuItem("New"); -// menuItem.setMnemonic(KeyEvent.VK_N); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_N, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("new"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// //Set up the second menu item. -// menuItem = new JMenuItem("Quit"); -// menuItem.setMnemonic(KeyEvent.VK_Q); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_Q, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("quit"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// return menuBar; -// } - - private JMenuBar generateMenuBar() - { - JMenuBar menuBar = new JMenuBar(); - - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); - lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); - lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( - "Управление режимом отображения приложения"); - - { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); - systemLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(systemLookAndFeel); - } - - { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); - crossplatformLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(crossplatformLookAndFeel); - } - - JMenu testMenu = new JMenu("Тесты"); - testMenu.setMnemonic(KeyEvent.VK_T); - testMenu.getAccessibleContext().setAccessibleDescription( - "Тестовые команды"); - - { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); - addLogMessageItem.addActionListener((event) -> { - Logger.debug("Новая строка"); - }); - testMenu.add(addLogMessageItem); - } - - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; - } - - private void setLookAndFeel(String className) - { - try - { - UIManager.setLookAndFeel(className); - SwingUtilities.updateComponentTreeUI(this); - } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { - // just ignore - } - } -} diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java deleted file mode 100644 index ca0ce4426..000000000 --- a/robots/src/log/LogWindowSource.java +++ /dev/null @@ -1,89 +0,0 @@ -package log; - -import java.util.ArrayList; -import java.util.Collections; - -/** - * Что починить: - * 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются - * удерживаемыми в памяти) - * 2. Этот класс хранит активные сообщения лога, но в такой реализации он - * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено - * величиной m_iQueueLength (т.е. реально нужна очередь сообщений - * ограниченного размера) - */ -public class LogWindowSource -{ - private int m_iQueueLength; - - private ArrayList m_messages; - private final ArrayList m_listeners; - private volatile LogChangeListener[] m_activeListeners; - - public LogWindowSource(int iQueueLength) - { - m_iQueueLength = iQueueLength; - m_messages = new ArrayList(iQueueLength); - m_listeners = new ArrayList(); - } - - public void registerListener(LogChangeListener listener) - { - synchronized(m_listeners) - { - m_listeners.add(listener); - m_activeListeners = null; - } - } - - public void unregisterListener(LogChangeListener listener) - { - synchronized(m_listeners) - { - m_listeners.remove(listener); - m_activeListeners = null; - } - } - - public void append(LogLevel logLevel, String strMessage) - { - LogEntry entry = new LogEntry(logLevel, strMessage); - m_messages.add(entry); - LogChangeListener [] activeListeners = m_activeListeners; - if (activeListeners == null) - { - synchronized (m_listeners) - { - if (m_activeListeners == null) - { - activeListeners = m_listeners.toArray(new LogChangeListener [0]); - m_activeListeners = activeListeners; - } - } - } - for (LogChangeListener listener : activeListeners) - { - listener.onLogChanged(); - } - } - - public int size() - { - return m_messages.size(); - } - - public Iterable range(int startFrom, int count) - { - if (startFrom < 0 || startFrom >= m_messages.size()) - { - return Collections.emptyList(); - } - int indexTo = Math.min(startFrom + count, m_messages.size()); - return m_messages.subList(startFrom, indexTo); - } - - public Iterable all() - { - return m_messages; - } -} diff --git a/robots/.classpath b/src/main/java/robots/.classpath similarity index 100% rename from robots/.classpath rename to src/main/java/robots/.classpath diff --git a/robots/.gitignore b/src/main/java/robots/.gitignore similarity index 100% rename from robots/.gitignore rename to src/main/java/robots/.gitignore diff --git a/robots/.project b/src/main/java/robots/.project similarity index 100% rename from robots/.project rename to src/main/java/robots/.project diff --git a/src/main/java/robots/gui/AbstractWindow.java b/src/main/java/robots/gui/AbstractWindow.java new file mode 100644 index 000000000..824df2c7c --- /dev/null +++ b/src/main/java/robots/gui/AbstractWindow.java @@ -0,0 +1,34 @@ +package robots.gui; + +import javax.swing.*; +import javax.swing.event.InternalFrameAdapter; +import javax.swing.event.InternalFrameEvent; + +public abstract class AbstractWindow extends JInternalFrame { + + public AbstractWindow(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable) { + super(title, resizable, closable, maximizable, iconifiable); + setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE); + + addInternalFrameListener(new InternalFrameAdapter() { + @Override + public void internalFrameClosing(InternalFrameEvent e) { + int result = JOptionPane.showConfirmDialog( + AbstractWindow.this, + "Вы уверены, что хотите закрыть окно?", + "Подтверждение", + JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + dispose(); + } + } + }); } + + protected abstract void closeWindow(); + + @Override + public void dispose() { + closeWindow(); + super.dispose(); + } +} \ No newline at end of file diff --git a/robots/src/gui/GameVisualizer.java b/src/main/java/robots/gui/GameVisualizer.java similarity index 73% rename from robots/src/gui/GameVisualizer.java rename to src/main/java/robots/gui/GameVisualizer.java index f82cfd8f8..dc2662740 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/src/main/java/robots/gui/GameVisualizer.java @@ -1,4 +1,4 @@ -package gui; +package robots.gui; import java.awt.Color; import java.awt.EventQueue; @@ -16,24 +16,24 @@ public class GameVisualizer extends JPanel { private final Timer m_timer = initTimer(); - - private static Timer initTimer() + + private static Timer initTimer() { Timer timer = new Timer("events generator", true); return timer; } - - private volatile double m_robotPositionX = 100; - private volatile double m_robotPositionY = 100; - private volatile double m_robotDirection = 0; - private volatile int m_targetPositionX = 150; - private volatile int m_targetPositionY = 100; - - private static final double maxVelocity = 0.1; - private static final double maxAngularVelocity = 0.001; - - public GameVisualizer() + public volatile double m_robotPositionX = 100; + public volatile double m_robotPositionY = 100; + volatile double m_robotDirection = 0; + + volatile int m_targetPositionX = 150; + volatile int m_targetPositionY = 100; + + private static final double maxVelocity = 0.1; + private static final double maxAngularVelocity = 0.001; + + public GameVisualizer() { m_timer.schedule(new TimerTask() { @@ -63,12 +63,16 @@ public void mouseClicked(MouseEvent e) setDoubleBuffered(true); } - protected void setTargetPosition(Point p) + public void setTargetPosition(Point p) { m_targetPositionX = p.x; m_targetPositionY = p.y; } - + + public Point getTargetPoint() { + return new Point(m_targetPositionX, m_targetPositionY); + } + protected void onRedrawEvent() { EventQueue.invokeLater(this::repaint); @@ -80,26 +84,30 @@ private static double distance(double x1, double y1, double x2, double y2) double diffY = y1 - y2; return Math.sqrt(diffX * diffX + diffY * diffY); } - + private static double angleTo(double fromX, double fromY, double toX, double toY) { double diffX = toX - fromX; double diffY = toY - fromY; - + return asNormalizedRadians(Math.atan2(diffY, diffX)); } - - protected void onModelUpdateEvent() + + public void onModelUpdateEvent() { - double distance = distance(m_targetPositionX, m_targetPositionY, - m_robotPositionX, m_robotPositionY); - if (distance < 0.5) + m_targetPositionX = (int) applyLimits(m_targetPositionX, 0, this.getWidth()); + m_targetPositionY = (int) applyLimits(m_targetPositionY, 0, this.getHeight()); + + double distance = distance(m_targetPositionX, m_targetPositionY, + m_robotPositionX, m_robotPositionY); + if (distance < 7) { return; } - double velocity = maxVelocity; + double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); double angularVelocity = 0; + if (angleToTarget > m_robotDirection) { angularVelocity = maxAngularVelocity; @@ -108,10 +116,10 @@ protected void onModelUpdateEvent() { angularVelocity = -maxAngularVelocity; } - - moveRobot(velocity, angularVelocity, 10); + + moveRobot(maxVelocity, angularVelocity, 10); } - + private static double applyLimits(double value, double min, double max) { if (value < min) @@ -120,28 +128,34 @@ private static double applyLimits(double value, double min, double max) return max; return value; } - - private void moveRobot(double velocity, double angularVelocity, double duration) + + void moveRobot(double velocity, double angularVelocity, double duration) { + m_robotDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); velocity = applyLimits(velocity, 0, maxVelocity); angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); - double newX = m_robotPositionX + velocity / angularVelocity * - (Math.sin(m_robotDirection + angularVelocity * duration) - - Math.sin(m_robotDirection)); + double newX = m_robotPositionX + velocity / angularVelocity * + (Math.sin(m_robotDirection + angularVelocity * duration) - + Math.sin(m_robotDirection)); if (!Double.isFinite(newX)) { newX = m_robotPositionX + velocity * duration * Math.cos(m_robotDirection); } - double newY = m_robotPositionY - velocity / angularVelocity * - (Math.cos(m_robotDirection + angularVelocity * duration) - - Math.cos(m_robotDirection)); + double newY = m_robotPositionY - velocity / angularVelocity * + (Math.cos(m_robotDirection + angularVelocity * duration) - + Math.cos(m_robotDirection)); if (!Double.isFinite(newY)) { newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); } + + newX = applyLimits(newX, 0, getWidth()); + newY = applyLimits(newY, 0, getHeight()); + m_robotPositionX = newX; m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); + + double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); m_robotDirection = newDirection; } @@ -157,37 +171,40 @@ private static double asNormalizedRadians(double angle) } return angle; } - + private static int round(double value) { return (int)(value + 0.5); } - + @Override public void paint(Graphics g) { super.paint(g); - Graphics2D g2d = (Graphics2D)g; + Graphics2D g2d = (Graphics2D)g; drawRobot(g2d, round(m_robotPositionX), round(m_robotPositionY), m_robotDirection); drawTarget(g2d, m_targetPositionX, m_targetPositionY); } - + private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); } - + private static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); } - + private void drawRobot(Graphics2D g, int x, int y, double direction) { - int robotCenterX = round(m_robotPositionX); + int robotCenterX = round(m_robotPositionX); int robotCenterY = round(m_robotPositionY); - AffineTransform t = AffineTransform.getRotateInstance(direction, robotCenterX, robotCenterY); - g.setTransform(t); + + AffineTransform originalTransform = g.getTransform(); + + g.rotate(direction, robotCenterX, robotCenterY); + g.setColor(Color.MAGENTA); fillOval(g, robotCenterX, robotCenterY, 30, 10); g.setColor(Color.BLACK); @@ -196,15 +213,15 @@ private void drawRobot(Graphics2D g, int x, int y, double direction) fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); g.setColor(Color.BLACK); drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); + + g.setTransform(originalTransform); } - + private void drawTarget(Graphics2D g, int x, int y) { - AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); - g.setTransform(t); g.setColor(Color.GREEN); fillOval(g, x, y, 5, 5); g.setColor(Color.BLACK); drawOval(g, x, y, 5, 5); } -} +} \ No newline at end of file diff --git a/robots/src/gui/GameWindow.java b/src/main/java/robots/gui/GameWindow.java similarity index 77% rename from robots/src/gui/GameWindow.java rename to src/main/java/robots/gui/GameWindow.java index ecb63c00f..fb7b3de92 100644 --- a/robots/src/gui/GameWindow.java +++ b/src/main/java/robots/gui/GameWindow.java @@ -1,11 +1,10 @@ -package gui; +package robots.gui; import java.awt.BorderLayout; -import javax.swing.JInternalFrame; import javax.swing.JPanel; -public class GameWindow extends JInternalFrame +public class GameWindow extends AbstractWindow { private final GameVisualizer m_visualizer; public GameWindow() @@ -17,4 +16,8 @@ public GameWindow() getContentPane().add(panel); pack(); } + + @Override + protected void closeWindow() { + } } diff --git a/robots/src/gui/LogWindow.java b/src/main/java/robots/gui/LogWindow.java similarity index 75% rename from robots/src/gui/LogWindow.java rename to src/main/java/robots/gui/LogWindow.java index 723d3e2fc..c4175d4dc 100644 --- a/robots/src/gui/LogWindow.java +++ b/src/main/java/robots/gui/LogWindow.java @@ -1,29 +1,28 @@ -package gui; +package robots.gui; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.TextArea; -import javax.swing.JInternalFrame; import javax.swing.JPanel; -import log.LogChangeListener; -import log.LogEntry; -import log.LogWindowSource; +import robots.log.LogChangeListener; +import robots.log.LogEntry; +import robots.log.LogWindowSource; -public class LogWindow extends JInternalFrame implements LogChangeListener +public class LogWindow extends AbstractWindow implements LogChangeListener { private LogWindowSource m_logSource; private TextArea m_logContent; - public LogWindow(LogWindowSource logSource) + public LogWindow(LogWindowSource logSource) { super("Протокол работы", true, true, true, true); m_logSource = logSource; m_logSource.registerListener(this); m_logContent = new TextArea(""); m_logContent.setSize(200, 500); - + JPanel panel = new JPanel(new BorderLayout()); panel.add(m_logContent, BorderLayout.CENTER); getContentPane().add(panel); @@ -41,10 +40,15 @@ private void updateLogContent() m_logContent.setText(content.toString()); m_logContent.invalidate(); } - + + @Override + protected void closeWindow() { + m_logSource.unregisterListener(this); + } + @Override public void onLogChanged() { EventQueue.invokeLater(this::updateLogContent); } -} +} \ No newline at end of file diff --git a/src/main/java/robots/gui/MainApplicationFrame.java b/src/main/java/robots/gui/MainApplicationFrame.java new file mode 100644 index 000000000..79b83999d --- /dev/null +++ b/src/main/java/robots/gui/MainApplicationFrame.java @@ -0,0 +1,175 @@ +package robots.gui; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JDesktopPane; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.*; + +import robots.log.Logger; + +public class MainApplicationFrame extends JFrame +{ + private final JDesktopPane desktopPane = new JDesktopPane(); + + public MainApplicationFrame() { + + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset*2, + screenSize.height - inset*2); + + setContentPane(desktopPane); + + + LogWindow logWindow = createLogWindow(); + addWindow(logWindow); + + GameWindow gameWindow = createGameWindow(); + addWindow(gameWindow); + + setJMenuBar(generateMenuBar()); + + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + int result = JOptionPane.showConfirmDialog(MainApplicationFrame.this, + "Вы уверены, что хотите выйти?", + "Подтверждение", JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } + } + }); + } + + protected GameWindow createGameWindow() + { + GameWindow gameWindow = new GameWindow(); + gameWindow.setSize(400, 400); + return gameWindow; + } + + protected LogWindow createLogWindow() + { + LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); + logWindow.setLocation(10,10); + logWindow.setSize(300, 800); + logWindow.pack(); + Logger.debug("Протокол работает"); + return logWindow; + } + + protected void addWindow(JInternalFrame frame) + { + desktopPane.add(frame); + frame.setVisible(true); + } + + private JMenu createLookAndFeelMenu() { + JMenu lookAndFeelMenu = new JMenu("Режим отображения"); + lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); + lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( + "Управление режимом отображения приложения"); + + JMenuItem systemLookAndFeel = createLookAndFeelMenuItem("Системная схема", UIManager.getSystemLookAndFeelClassName()); + JMenuItem crossplatformLookAndFeel = createLookAndFeelMenuItem("Универсальная схема", UIManager.getCrossPlatformLookAndFeelClassName()); + + lookAndFeelMenu.add(systemLookAndFeel); + lookAndFeelMenu.add(crossplatformLookAndFeel); + + return lookAndFeelMenu; + } + + private JMenuItem createLookAndFeelMenuItem(String name, String className) { + JMenuItem menuItem = new JMenuItem(name); + menuItem.setMnemonic(KeyEvent.VK_S); + menuItem.addActionListener((event) -> { + setLookAndFeel(className); + this.invalidate(); + }); + + return menuItem; + } + + private JMenu createTestMenu() { + JMenu testMenu = new JMenu("Тесты"); + testMenu.setMnemonic(KeyEvent.VK_T); + testMenu.getAccessibleContext().setAccessibleDescription( + "Тестовые команды"); + + JMenuItem addLogMessageItem = createTestMenuItem("Сообщение в лог", "Новая строка"); + + testMenu.add(addLogMessageItem); + + return testMenu; + } + + private JMenuItem createTestMenuItem(String name, String logMessage) { + JMenuItem menuItem = new JMenuItem(name); + menuItem.setMnemonic(KeyEvent.VK_S); + menuItem.addActionListener((event) -> { + Logger.debug(logMessage); + }); + + return menuItem; + } + + private JMenuItem createExitMenuItem() { + JMenuItem exitMenuItem = new JMenuItem("Выход"); + exitMenuItem.setMnemonic(KeyEvent.VK_T); + exitMenuItem.addActionListener((event) -> { + int result = JOptionPane.showConfirmDialog(null, + "Вы уверены, что хотите выйти?", "Подтверждение", + JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } + }); + + exitMenuItem.setPreferredSize(new Dimension(62, 20)); + exitMenuItem.setMaximumSize(new Dimension(62, 20)); + + return exitMenuItem; + } + + private JMenuBar generateMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + JMenu lookAndFeelMenu = createLookAndFeelMenu(); + JMenu testMenu = createTestMenu(); + JMenuItem exitMenuItem = createExitMenuItem(); + + menuBar.add(lookAndFeelMenu); + menuBar.add(testMenu); + menuBar.add(exitMenuItem); + + return menuBar; + } + + private void setLookAndFeel(String className) + { + try + { + UIManager.setLookAndFeel(className); + SwingUtilities.updateComponentTreeUI(this); + } + catch (ClassNotFoundException | InstantiationException + | IllegalAccessException | UnsupportedLookAndFeelException e) + { + } + } +} \ No newline at end of file diff --git a/robots/src/gui/RobotsProgram.java b/src/main/java/robots/gui/RobotsProgram.java similarity index 68% rename from robots/src/gui/RobotsProgram.java rename to src/main/java/robots/gui/RobotsProgram.java index ae0930a8b..1454cbe02 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/src/main/java/robots/gui/RobotsProgram.java @@ -1,4 +1,4 @@ -package gui; +package robots.gui; import java.awt.Frame; @@ -10,9 +10,6 @@ public class RobotsProgram public static void main(String[] args) { try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); -// UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); -// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); -// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } diff --git a/robots/src/log/LogChangeListener.java b/src/main/java/robots/log/LogChangeListener.java similarity index 78% rename from robots/src/log/LogChangeListener.java rename to src/main/java/robots/log/LogChangeListener.java index 0b0fb85dd..2553a375a 100644 --- a/robots/src/log/LogChangeListener.java +++ b/src/main/java/robots/log/LogChangeListener.java @@ -1,4 +1,4 @@ -package log; +package robots.log; public interface LogChangeListener { diff --git a/robots/src/log/LogEntry.java b/src/main/java/robots/log/LogEntry.java similarity index 95% rename from robots/src/log/LogEntry.java rename to src/main/java/robots/log/LogEntry.java index 3d9147107..ab539b24d 100644 --- a/robots/src/log/LogEntry.java +++ b/src/main/java/robots/log/LogEntry.java @@ -1,4 +1,4 @@ -package log; +package robots.log; public class LogEntry { diff --git a/robots/src/log/LogLevel.java b/src/main/java/robots/log/LogLevel.java similarity index 93% rename from robots/src/log/LogLevel.java rename to src/main/java/robots/log/LogLevel.java index 582d010cc..898e8dbfb 100644 --- a/robots/src/log/LogLevel.java +++ b/src/main/java/robots/log/LogLevel.java @@ -1,4 +1,4 @@ -package log; +package robots.log; public enum LogLevel { diff --git a/src/main/java/robots/log/LogWindowSource.java b/src/main/java/robots/log/LogWindowSource.java new file mode 100644 index 000000000..7f4a7604c --- /dev/null +++ b/src/main/java/robots/log/LogWindowSource.java @@ -0,0 +1,76 @@ +package robots.log; + +import java.util.ArrayList; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + + +public class LogWindowSource { + private final int m_iQueueLength; + + private final BlockingQueue m_messages; + private final ArrayList m_listeners; + private volatile LogChangeListener[] m_activeListeners; + + public LogWindowSource(int iQueueLength) { + m_iQueueLength = iQueueLength; + m_messages = new ArrayBlockingQueue(iQueueLength); + m_listeners = new ArrayList(); + } + + public void registerListener(LogChangeListener listener) { + m_listeners.add(listener); + updateActiveListeners(); + } + + public void unregisterListener(LogChangeListener listener) { + m_listeners.remove(listener); + updateActiveListeners(); + } + + public void append(LogLevel logLevel, String strMessage) { + LogEntry entry = new LogEntry(logLevel, strMessage); + boolean result = false; + do { + result = m_messages.offer(entry); + if (!result) { + m_messages.poll(); + } + } while (!result); + + notifyListeners(); + } + + public void notifyListeners() { + LogChangeListener[] activeListeners = m_activeListeners; + + if (activeListeners != null) { + for (LogChangeListener listener : activeListeners) { + listener.onLogChanged(); + } + } + } + + + private void updateActiveListeners() { + m_activeListeners = m_listeners.toArray(new LogChangeListener[0]); + } + + public int size() { + return m_messages.size(); + } + + public int getCountListener() { + return m_listeners.size(); + } + + public Iterable range(int startFrom, int count) { + ArrayList range = new ArrayList<>(m_messages); + int end = Math.min(startFrom + count, range.size()); + return range.subList(startFrom, end); + } + + public Iterable all() { + return new ArrayList<>(m_messages); + } +} \ No newline at end of file diff --git a/robots/src/log/Logger.java b/src/main/java/robots/log/Logger.java similarity index 87% rename from robots/src/log/Logger.java rename to src/main/java/robots/log/Logger.java index b008a5d01..fc1d6f341 100644 --- a/robots/src/log/Logger.java +++ b/src/main/java/robots/log/Logger.java @@ -1,10 +1,10 @@ -package log; +package robots.log; public final class Logger { private static final LogWindowSource defaultLogSource; static { - defaultLogSource = new LogWindowSource(100); + defaultLogSource = new LogWindowSource(3); } private Logger() diff --git a/src/test/java/robots/log/LogWindowSourceTest.java b/src/test/java/robots/log/LogWindowSourceTest.java new file mode 100644 index 000000000..6d80b5a57 --- /dev/null +++ b/src/test/java/robots/log/LogWindowSourceTest.java @@ -0,0 +1,75 @@ +package robots.log; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import robots.gui.GameVisualizer; + +import java.awt.*; + +import static org.junit.jupiter.api.Assertions.*; + +class LogWindowSourceTest { + private LogWindowSource logWindowSource; + + @BeforeEach + public void setUp() { + logWindowSource = new LogWindowSource(5); + } + + @Test + void testRegisterListener() { + LogChangeListener listener = new LogChangeListener() { + @Override + public void onLogChanged() { + } + }; + logWindowSource.registerListener(listener); + assertEquals(1, logWindowSource.getCountListener()); + } + + @Test + void testUnregisterListener() { + LogChangeListener listener = new LogChangeListener() { + @Override + public void onLogChanged() { + } + }; + logWindowSource.registerListener(listener); + logWindowSource.unregisterListener(listener); + + assertEquals(0, logWindowSource.getCountListener()); + } + + @Test + void testAppend() { + LogChangeListener listener = new LogChangeListener() { + @Override + public void onLogChanged() { + } + }; + logWindowSource.registerListener(listener); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + assertEquals(3, logWindowSource.size()); + } + + @Test + void testOverflowQueue() { + LogChangeListener listener = new LogChangeListener() { + @Override + public void onLogChanged() { + } + }; + logWindowSource.registerListener(listener); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + logWindowSource.append(LogLevel.Info, "message"); + assertEquals(5, logWindowSource.size()); + } +} \ No newline at end of file