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