From 21d0f404f95d4f84a78938272108ac235ce27b84 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Tue, 27 Feb 2024 00:01:39 +0500 Subject: [PATCH 01/12] Update MainApplicationFrame.java --- robots/src/gui/MainApplicationFrame.java | 113 +++++++++++++---------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 62e943ee1..12ed7fd19 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -2,42 +2,35 @@ import java.awt.Dimension; import java.awt.Toolkit; +import java.awt.event.ActionEvent; 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 javax.swing.*; import log.Logger; /** * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. + * 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; + 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); @@ -48,7 +41,7 @@ public MainApplicationFrame() { setJMenuBar(generateMenuBar()); setDefaultCloseOperation(EXIT_ON_CLOSE); } - + protected LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); @@ -59,51 +52,57 @@ protected LogWindow createLogWindow() 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; -// } - + + protected JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + JMenu menu = new JMenu("File"); + menuBar.add(menu); + + JMenuItem menuItem = new JMenuItem("New", KeyEvent.VK_N); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)); + menu.add(menuItem); + + menuItem = new JMenuItem("Open", KeyEvent.VK_O); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK)); + menu.add(menuItem); + + menuItem = new JMenuItem("Save", KeyEvent.VK_S); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)); + menu.add(menuItem); + + menuItem = new JMenuItem("Exit", KeyEvent.VK_Q); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); + menu.add(menuItem); + + JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_E); + exitItem.addActionListener((event) -> { + int result = JOptionPane.showConfirmDialog(this, "Вы уверены, что хотите выйти из приложения?", "Выход", + JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } + }); + menu.add(exitItem); + + 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) -> { @@ -126,7 +125,7 @@ private JMenuBar generateMenuBar() testMenu.setMnemonic(KeyEvent.VK_T); testMenu.getAccessibleContext().setAccessibleDescription( "Тестовые команды"); - + { JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); addLogMessageItem.addActionListener((event) -> { @@ -134,12 +133,26 @@ private JMenuBar generateMenuBar() }); testMenu.add(addLogMessageItem); } + JMenuItem exitItem = new JMenuItem("Выход", KeyEvent.VK_E); + exitItem.addActionListener((event) -> { + UIManager.put("OptionPane.yesButtonText", "Да");; + UIManager.put("OptionPane.noButtonText", "Нет"); + UIManager.put("OptionPane.cancelButtonText", "Отмена"); + int result = JOptionPane.showConfirmDialog(this, "Вы уверены, что хотите выйти из приложения?", "Выход", + JOptionPane.YES_NO_CANCEL_OPTION); + + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } + }); + menuBar.add(lookAndFeelMenu); menuBar.add(testMenu); + menuBar.add(exitItem); return menuBar; } - + private void setLookAndFeel(String className) { try From 82d26fcda95457784ee65d32c9603fd5cf2c6d37 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:01:47 +0500 Subject: [PATCH 02/12] Update MainApplicationFrame.java --- robots/src/gui/MainApplicationFrame.java | 51 +++--------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 12ed7fd19..e94f7ecae 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -2,19 +2,11 @@ import java.awt.Dimension; import java.awt.Toolkit; -import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import javax.swing.*; import log.Logger; - -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); @@ -37,9 +29,10 @@ public MainApplicationFrame() { GameWindow gameWindow = new GameWindow(); gameWindow.setSize(400, 400); addWindow(gameWindow); - + addWindow(gameWindow.m_visualizer.robotCoordinatesWindow); setJMenuBar(generateMenuBar()); setDefaultCloseOperation(EXIT_ON_CLOSE); + WindowStateManager.loadWindowStateFromFile(desktopPane); } protected LogWindow createLogWindow() @@ -52,48 +45,12 @@ protected LogWindow createLogWindow() Logger.debug("Протокол работает"); return logWindow; } - protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); } - protected JMenuBar createMenuBar() { - JMenuBar menuBar = new JMenuBar(); - - JMenu menu = new JMenu("File"); - menuBar.add(menu); - - JMenuItem menuItem = new JMenuItem("New", KeyEvent.VK_N); - menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)); - menu.add(menuItem); - - menuItem = new JMenuItem("Open", KeyEvent.VK_O); - menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK)); - menu.add(menuItem); - - menuItem = new JMenuItem("Save", KeyEvent.VK_S); - menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)); - menu.add(menuItem); - - menuItem = new JMenuItem("Exit", KeyEvent.VK_Q); - menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); - menu.add(menuItem); - - JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_E); - exitItem.addActionListener((event) -> { - int result = JOptionPane.showConfirmDialog(this, "Вы уверены, что хотите выйти из приложения?", "Выход", - JOptionPane.YES_NO_OPTION); - if (result == JOptionPane.YES_OPTION) { - System.exit(0); - } - }); - menu.add(exitItem); - - return menuBar; - } - private JMenuBar generateMenuBar() { JMenuBar menuBar = new JMenuBar(); @@ -135,9 +92,10 @@ private JMenuBar generateMenuBar() } JMenuItem exitItem = new JMenuItem("Выход", KeyEvent.VK_E); exitItem.addActionListener((event) -> { + WindowStateManager.saveWindowStateToFile(desktopPane); UIManager.put("OptionPane.yesButtonText", "Да");; UIManager.put("OptionPane.noButtonText", "Нет"); - UIManager.put("OptionPane.cancelButtonText", "Отмена"); + UIManager.put("OptionPane.cancelButtonText", "Извиняюсь!"); int result = JOptionPane.showConfirmDialog(this, "Вы уверены, что хотите выйти из приложения?", "Выход", JOptionPane.YES_NO_CANCEL_OPTION); @@ -167,3 +125,4 @@ private void setLookAndFeel(String className) } } } + From 1cc836bec651094e546a2ffba0d6ab902ac509bf Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:05:21 +0500 Subject: [PATCH 03/12] Update GameWindow.java --- robots/src/gui/GameWindow.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index ecb63c00f..4c3f8bc06 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -7,11 +7,10 @@ public class GameWindow extends JInternalFrame { - private final GameVisualizer m_visualizer; - public GameWindow() + public final GameVisualizer m_visualizer = new GameVisualizer(); + public GameWindow() { super("Игровое поле", true, true, true, true); - m_visualizer = new GameVisualizer(); JPanel panel = new JPanel(new BorderLayout()); panel.add(m_visualizer, BorderLayout.CENTER); getContentPane().add(panel); From 29d1aff6fefe9db4caaa2fdc16d60e10b6c33dc4 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:06:01 +0500 Subject: [PATCH 04/12] Update GameVisualizer.java --- robots/src/gui/GameVisualizer.java | 138 ++++------------------------- 1 file changed, 15 insertions(+), 123 deletions(-) diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index f82cfd8f8..4e96c1090 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -4,36 +4,27 @@ import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.util.Timer; import java.util.TimerTask; -import javax.swing.JPanel; +import javax.swing.*; + +import static gui.RobotModel.round; public class GameVisualizer extends JPanel { + public RobotModel robotModel = new RobotModel(); + public RobotCoordinatesWindow robotCoordinatesWindow = new RobotCoordinatesWindow(robotModel); 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 GameVisualizer() { m_timer.schedule(new TimerTask() { @@ -48,7 +39,7 @@ public void run() @Override public void run() { - onModelUpdateEvent(); + robotModel.onModelUpdateEvent(); } }, 0, 10); addMouseListener(new MouseAdapter() @@ -56,120 +47,23 @@ public void run() @Override public void mouseClicked(MouseEvent e) { - setTargetPosition(e.getPoint()); + robotModel.setTargetPosition(e.getPoint()); repaint(); } }); setDoubleBuffered(true); } - - protected void setTargetPosition(Point p) - { - m_targetPositionX = p.x; - m_targetPositionY = p.y; - } - protected void onRedrawEvent() { EventQueue.invokeLater(this::repaint); } - - private static double distance(double x1, double y1, double x2, double y2) - { - double diffX = x1 - x2; - 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() - { - double distance = distance(m_targetPositionX, m_targetPositionY, - m_robotPositionX, m_robotPositionY); - if (distance < 0.5) - { - return; - } - double velocity = maxVelocity; - double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); - double angularVelocity = 0; - if (angleToTarget > m_robotDirection) - { - angularVelocity = maxAngularVelocity; - } - if (angleToTarget < m_robotDirection) - { - angularVelocity = -maxAngularVelocity; - } - - moveRobot(velocity, angularVelocity, 10); - } - - private static double applyLimits(double value, double min, double max) - { - if (value < min) - return min; - if (value > max) - return max; - return value; - } - - private void moveRobot(double velocity, double angularVelocity, double 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)); - 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)); - if (!Double.isFinite(newY)) - { - newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); - } - m_robotPositionX = newX; - m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; - } - - private static double asNormalizedRadians(double angle) - { - while (angle < 0) - { - angle += 2*Math.PI; - } - while (angle >= 2*Math.PI) - { - angle -= 2*Math.PI; - } - 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; - drawRobot(g2d, round(m_robotPositionX), round(m_robotPositionY), m_robotDirection); - drawTarget(g2d, m_targetPositionX, m_targetPositionY); + Graphics2D g2d = (Graphics2D)g; + drawRobot(g2d, round(robotModel.m_robotPositionX), round(robotModel.m_robotPositionY), robotModel.m_robotDirection); + drawTarget(g2d, robotModel.m_targetPositionX, robotModel.m_targetPositionY); } private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) @@ -181,12 +75,11 @@ private static void drawOval(Graphics g, int centerX, int centerY, int diam1, in { 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 robotCenterY = round(m_robotPositionY); - AffineTransform t = AffineTransform.getRotateInstance(direction, robotCenterX, robotCenterY); + int robotCenterX = round(robotModel.m_robotPositionX); + int robotCenterY = round(robotModel.m_robotPositionY); + AffineTransform t = AffineTransform.getRotateInstance(robotModel.m_robotDirection, robotCenterX, robotCenterY); g.setTransform(t); g.setColor(Color.MAGENTA); fillOval(g, robotCenterX, robotCenterY, 30, 10); @@ -197,7 +90,6 @@ private void drawRobot(Graphics2D g, int x, int y, double direction) g.setColor(Color.BLACK); drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); } - private void drawTarget(Graphics2D g, int x, int y) { AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); From 6e1851277d4ca533a0ddf5a5b847a8ff50f1d77d Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:07:08 +0500 Subject: [PATCH 05/12] Create RobotModel.java --- robots/src/gui/RobotModel.java | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 robots/src/gui/RobotModel.java diff --git a/robots/src/gui/RobotModel.java b/robots/src/gui/RobotModel.java new file mode 100644 index 000000000..4e96c1090 --- /dev/null +++ b/robots/src/gui/RobotModel.java @@ -0,0 +1,102 @@ +package gui; + +import java.awt.Color; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.*; + +import static gui.RobotModel.round; + +public class GameVisualizer extends JPanel +{ + public RobotModel robotModel = new RobotModel(); + public RobotCoordinatesWindow robotCoordinatesWindow = new RobotCoordinatesWindow(robotModel); + private final Timer m_timer = initTimer(); + private static Timer initTimer() + { + Timer timer = new Timer("events generator", true); + return timer; + } + public GameVisualizer() + { + m_timer.schedule(new TimerTask() + { + @Override + public void run() + { + onRedrawEvent(); + } + }, 0, 50); + m_timer.schedule(new TimerTask() + { + @Override + public void run() + { + robotModel.onModelUpdateEvent(); + } + }, 0, 10); + addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + robotModel.setTargetPosition(e.getPoint()); + repaint(); + } + }); + setDoubleBuffered(true); + } + protected void onRedrawEvent() + { + EventQueue.invokeLater(this::repaint); + } + @Override + public void paint(Graphics g) + { + super.paint(g); + Graphics2D g2d = (Graphics2D)g; + drawRobot(g2d, round(robotModel.m_robotPositionX), round(robotModel.m_robotPositionY), robotModel.m_robotDirection); + drawTarget(g2d, robotModel.m_targetPositionX, robotModel.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(robotModel.m_robotPositionX); + int robotCenterY = round(robotModel.m_robotPositionY); + AffineTransform t = AffineTransform.getRotateInstance(robotModel.m_robotDirection, robotCenterX, robotCenterY); + g.setTransform(t); + g.setColor(Color.MAGENTA); + fillOval(g, robotCenterX, robotCenterY, 30, 10); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX, robotCenterY, 30, 10); + g.setColor(Color.WHITE); + fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); + } + 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); + } +} From 283e0675693e8cc10ab52f8360b3f5766e47cd93 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:08:03 +0500 Subject: [PATCH 06/12] Create RobotCoordinatesWindow.java --- robots/src/gui/RobotCoordinatesWindow.java | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 robots/src/gui/RobotCoordinatesWindow.java diff --git a/robots/src/gui/RobotCoordinatesWindow.java b/robots/src/gui/RobotCoordinatesWindow.java new file mode 100644 index 000000000..f728b8315 --- /dev/null +++ b/robots/src/gui/RobotCoordinatesWindow.java @@ -0,0 +1,25 @@ +package gui; + +import javax.swing.*; +import java.util.Observable; +import java.util.Observer; + +public class RobotCoordinatesWindow extends JInternalFrame implements Observer { + private final JLabel coordinatesLabel; + public RobotCoordinatesWindow(RobotModel model) { + super("Координаты робота"); + model.addObserver(this); + coordinatesLabel = new JLabel("X: " + model.getX() + ", Y: " + model.getY()); + this.add(coordinatesLabel); + this.setSize(300, 300); + this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + this.setVisible(true); + } + @Override + public void update(Observable o, Object arg) { + if (o instanceof RobotModel) { + RobotModel model = (RobotModel) o; + coordinatesLabel.setText("X: " + model.getX() + ", Y: " + model.getY()); + } + } +} From 8b73b7fd042b1ac66fde621a618be76882e1f478 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:54:31 +0500 Subject: [PATCH 07/12] Update RobotModel.java --- robots/src/gui/RobotModel.java | 213 ++++++++++++++++++++------------- 1 file changed, 130 insertions(+), 83 deletions(-) diff --git a/robots/src/gui/RobotModel.java b/robots/src/gui/RobotModel.java index 4e96c1090..3208fea69 100644 --- a/robots/src/gui/RobotModel.java +++ b/robots/src/gui/RobotModel.java @@ -1,102 +1,149 @@ package gui; -import java.awt.Color; -import java.awt.EventQueue; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.geom.AffineTransform; -import java.util.Timer; -import java.util.TimerTask; - -import javax.swing.*; - -import static gui.RobotModel.round; - -public class GameVisualizer extends JPanel -{ - public RobotModel robotModel = new RobotModel(); - public RobotCoordinatesWindow robotCoordinatesWindow = new RobotCoordinatesWindow(robotModel); - private final Timer m_timer = initTimer(); - private static Timer initTimer() +import java.awt.Point; +import java.util.Observable; +import static java.lang.Math.abs; + + +public class RobotModel extends Observable{ + + protected volatile double m_robotPositionX = 100; + protected volatile double m_robotPositionY = 100; + protected volatile double m_robotDirection = 0; + + protected volatile int m_targetPositionX = 150; + protected volatile int m_targetPositionY = 100; + + private static final double maxVelocity = 0.1; + private static final double maxAngularVelocity = 0.001*3; + + + protected void setTargetPosition(Point p) { - Timer timer = new Timer("events generator", true); - return timer; + m_targetPositionX = p.x*2; + m_targetPositionY = p.y*2; } - public GameVisualizer() + static int round(double value) { - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onRedrawEvent(); - } - }, 0, 50); - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - robotModel.onModelUpdateEvent(); - } - }, 0, 10); - addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - robotModel.setTargetPosition(e.getPoint()); - repaint(); - } - }); - setDoubleBuffered(true); + return (int)(value + 0.5); } - protected void onRedrawEvent() + private static double distance(double x1, double y1, double x2, double y2) { - EventQueue.invokeLater(this::repaint); + double diffX = x1 - x2; + double diffY = y1 - y2; + return Math.sqrt(diffX * diffX + diffY * diffY); } - @Override - public void paint(Graphics g) + + private static double angleTo(double fromX, double fromY, double toX, double toY) { - super.paint(g); - Graphics2D g2d = (Graphics2D)g; - drawRobot(g2d, round(robotModel.m_robotPositionX), round(robotModel.m_robotPositionY), robotModel.m_robotDirection); - drawTarget(g2d, robotModel.m_targetPositionX, robotModel.m_targetPositionY); + double diffX = toX - fromX; + double diffY = toY - fromY; + + return asNormalizedRadians(Math.atan2(diffY, diffX)); } - - private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) + + protected void onModelUpdateEvent() { - g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); + double distance = distance(m_targetPositionX, m_targetPositionY, + m_robotPositionX, m_robotPositionY); + if (distance < 0.5) + { + return; + } + double velocity = maxVelocity; + double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); + double angularVelocity = 0; + if (abs(angleToTarget - m_robotDirection) < Math.PI) { + if (angleToTarget > m_robotDirection) { + angularVelocity = maxAngularVelocity; + } + else if (angleToTarget < m_robotDirection) { + angularVelocity = -maxAngularVelocity; + } + } + else { + if (angleToTarget > m_robotDirection) { + angularVelocity = -maxAngularVelocity; + } + else if (angleToTarget < m_robotDirection) { + angularVelocity = maxAngularVelocity; + } + } + + moveRobot(velocity, angularVelocity, 10); + setChanged(); + notifyObservers(); } - - private static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) + + + private static double applyLimits(double value, double min, double max) { - g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); + if (value < min) + return min; + if (value > max) + return max; + return value; } - private void drawRobot(Graphics2D g, int x, int y, double direction) + + private void moveRobot(double velocity, double angularVelocity, double duration) { - int robotCenterX = round(robotModel.m_robotPositionX); - int robotCenterY = round(robotModel.m_robotPositionY); - AffineTransform t = AffineTransform.getRotateInstance(robotModel.m_robotDirection, robotCenterX, robotCenterY); - g.setTransform(t); - g.setColor(Color.MAGENTA); - fillOval(g, robotCenterX, robotCenterY, 30, 10); - g.setColor(Color.BLACK); - drawOval(g, robotCenterX, robotCenterY, 30, 10); - g.setColor(Color.WHITE); - fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); - g.setColor(Color.BLACK); - drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); + 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)); + + 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)); + if (!Double.isFinite(newY)) + { + newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); + } + if((abs(angleTo(m_robotPositionX,m_robotPositionY,m_targetPositionX,m_targetPositionY))<=Math.PI/6)&&(angleTo(m_robotPositionX,m_robotPositionY,m_targetPositionX,m_targetPositionY)>0)&&(distance(m_robotPositionX,m_robotPositionY,m_targetPositionX,m_targetPositionY)<60)){ + newX = m_robotPositionX + velocity * duration * Math.cos(m_robotDirection); + newY = m_robotPositionY + velocity * duration * -Math.sin(m_robotDirection); + m_robotDirection=asNormalizedRadians(m_robotDirection + angularVelocity * duration);; + m_robotPositionX = newX; + m_robotPositionY = newY; + } + else { + if (((m_robotPositionX<=0&&m_robotPositionX>-2.5) || (m_robotPositionY<=0&&m_robotPositionY>-2.5))){ + m_robotPositionX = m_robotPositionX + velocity * duration * -Math.cos(m_robotDirection); + m_robotPositionY = m_robotPositionY + velocity * duration * -Math.sin(m_robotDirection); + double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); + m_robotDirection = newDirection; + } + else { + m_robotPositionX = newX; + m_robotPositionY = newY; + double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); + m_robotDirection = newDirection; + } + } + } - private void drawTarget(Graphics2D g, int x, int y) + + private static double asNormalizedRadians(double angle) { - 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); + while (angle < 0) + { + angle += 2*Math.PI; + } + while (angle >= 2*Math.PI) + { + angle -= 2*Math.PI; + } + return angle; + } + public double getX() { + return m_robotPositionX; + } + public double getY() { + return m_robotPositionY; } } From 97808bd176d01da9008d73e8d23c86dfc3f68804 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Mon, 29 Apr 2024 20:48:45 +0500 Subject: [PATCH 08/12] Update RobotModel.java --- robots/src/gui/RobotModel.java | 70 +++++++++++++++------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/robots/src/gui/RobotModel.java b/robots/src/gui/RobotModel.java index 3208fea69..194742f58 100644 --- a/robots/src/gui/RobotModel.java +++ b/robots/src/gui/RobotModel.java @@ -85,48 +85,38 @@ private static double applyLimits(double value, double min, double max) return value; } - private void moveRobot(double velocity, double angularVelocity, double 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)); - - 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)); - if (!Double.isFinite(newY)) - { - newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); - } - if((abs(angleTo(m_robotPositionX,m_robotPositionY,m_targetPositionX,m_targetPositionY))<=Math.PI/6)&&(angleTo(m_robotPositionX,m_robotPositionY,m_targetPositionX,m_targetPositionY)>0)&&(distance(m_robotPositionX,m_robotPositionY,m_targetPositionX,m_targetPositionY)<60)){ - newX = m_robotPositionX + velocity * duration * Math.cos(m_robotDirection); - newY = m_robotPositionY + velocity * duration * -Math.sin(m_robotDirection); - m_robotDirection=asNormalizedRadians(m_robotDirection + angularVelocity * duration);; - m_robotPositionX = newX; - m_robotPositionY = newY; - } - else { - if (((m_robotPositionX<=0&&m_robotPositionX>-2.5) || (m_robotPositionY<=0&&m_robotPositionY>-2.5))){ - m_robotPositionX = m_robotPositionX + velocity * duration * -Math.cos(m_robotDirection); - m_robotPositionY = m_robotPositionY + velocity * duration * -Math.sin(m_robotDirection); - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; - } - else { - m_robotPositionX = newX; - m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; - } - } + private void moveRobot(double velocity, double angularVelocity, double 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)); + + 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)); + if (!Double.isFinite(newY)) { + newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); + } + + if (newX < 0) { + newX = 0; + m_robotDirection = Math.PI - m_robotDirection; } + if (newY < 0) { + newY = 0; + m_robotDirection = -m_robotDirection; + } + + + m_robotPositionX = newX; + m_robotPositionY = newY; + m_robotDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); +} private static double asNormalizedRadians(double angle) { From e006b427d3dd203dc3c492a1cb021dea5a593dcf Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Wed, 29 May 2024 10:47:07 +0500 Subject: [PATCH 09/12] Update MainApplicationFrame.java --- robots/src/gui/MainApplicationFrame.java | 109 ++++++++++++++++------- 1 file changed, 76 insertions(+), 33 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index e94f7ecae..83570bd54 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -3,65 +3,72 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.KeyEvent; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; import javax.swing.*; import log.Logger; -public class MainApplicationFrame extends JFrame -{ + +public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); + public static ResourceBundle messages; + private final ConcurrentHashMap messageFormatCache = new ConcurrentHashMap<>(); + private final GameWindow gameWindow; public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. + // Set default locale + changeLocale(Locale.getDefault()); + + // 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); + screenSize.width - inset * 2, + screenSize.height - inset * 2); setContentPane(desktopPane); - LogWindow logWindow = createLogWindow(); addWindow(logWindow); - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); + gameWindow = new GameWindow(); + gameWindow.setSize(400, 400); addWindow(gameWindow); addWindow(gameWindow.m_visualizer.robotCoordinatesWindow); setJMenuBar(generateMenuBar()); setDefaultCloseOperation(EXIT_ON_CLOSE); - WindowStateManager.loadWindowStateFromFile(desktopPane); + //WindowStateManager.loadWindowStateFromFile(desktopPane); } - protected LogWindow createLogWindow() - { + protected LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); - logWindow.setLocation(10,10); + logWindow.setLocation(10, 10); logWindow.setSize(300, 800); setMinimumSize(logWindow.getSize()); logWindow.pack(); Logger.debug("Протокол работает"); return logWindow; } - protected void addWindow(JInternalFrame frame) - { + + protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); } - private JMenuBar generateMenuBar() - { + private JMenuBar generateMenuBar() { JMenuBar menuBar = new JMenuBar(); - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); + JMenu lookAndFeelMenu = new JMenu(getMessage("menu.view")); lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( "Управление режимом отображения приложения"); { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); + JMenuItem systemLookAndFeel = new JMenuItem(getMessage("menu.view.system"), KeyEvent.VK_S); systemLookAndFeel.addActionListener((event) -> { setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); this.invalidate(); @@ -70,7 +77,7 @@ private JMenuBar generateMenuBar() } { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); + JMenuItem crossplatformLookAndFeel = new JMenuItem(getMessage("menu.view.crossplatform"), KeyEvent.VK_S); crossplatformLookAndFeel.addActionListener((event) -> { setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); this.invalidate(); @@ -78,25 +85,26 @@ private JMenuBar generateMenuBar() lookAndFeelMenu.add(crossplatformLookAndFeel); } - JMenu testMenu = new JMenu("Тесты"); + JMenu testMenu = new JMenu(getMessage("menu.tests")); testMenu.setMnemonic(KeyEvent.VK_T); testMenu.getAccessibleContext().setAccessibleDescription( "Тестовые команды"); { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); + JMenuItem addLogMessageItem = new JMenuItem(getMessage("menu.tests.addLogMessage"), KeyEvent.VK_S); addLogMessageItem.addActionListener((event) -> { Logger.debug("Новая строка"); }); testMenu.add(addLogMessageItem); } - JMenuItem exitItem = new JMenuItem("Выход", KeyEvent.VK_E); + + JMenuItem exitItem = new JMenuItem(getMessage("menu.exit"), KeyEvent.VK_E); exitItem.addActionListener((event) -> { WindowStateManager.saveWindowStateToFile(desktopPane); - UIManager.put("OptionPane.yesButtonText", "Да");; + UIManager.put("OptionPane.yesButtonText", "Да"); UIManager.put("OptionPane.noButtonText", "Нет"); UIManager.put("OptionPane.cancelButtonText", "Извиняюсь!"); - int result = JOptionPane.showConfirmDialog(this, "Вы уверены, что хотите выйти из приложения?", "Выход", + int result = JOptionPane.showConfirmDialog(this, getMessage("menu.exit.confirm"), getMessage("menu.exit"), JOptionPane.YES_NO_CANCEL_OPTION); if (result == JOptionPane.YES_OPTION) { @@ -104,25 +112,60 @@ private JMenuBar generateMenuBar() } }); + JMenu languageMenu = new JMenu("Язык"); + { + JMenuItem russianItem = new JMenuItem("Русский", KeyEvent.VK_R); + russianItem.addActionListener((event) -> { + changeLocale(Locale.forLanguageTag("ru-RU")); + updateUI(); + }); + languageMenu.add(russianItem); + } + { + JMenuItem englishItem = new JMenuItem("Английский", KeyEvent.VK_E); + englishItem.addActionListener((event) -> { + changeLocale(Locale.forLanguageTag("en-US")); + updateUI(); + }); + languageMenu.add(englishItem); + } menuBar.add(lookAndFeelMenu); menuBar.add(testMenu); menuBar.add(exitItem); + menuBar.add(languageMenu); return menuBar; } - private void setLookAndFeel(String className) - { - try - { + private void setLookAndFeel(String className) { + try { UIManager.setLookAndFeel(className); SwingUtilities.updateComponentTreeUI(this); - } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { + } catch (ClassNotFoundException | InstantiationException + | IllegalAccessException | UnsupportedLookAndFeelException e) { // just ignore } } + + private void changeLocale(Locale locale) { + Locale.setDefault(locale); + try { + messages = ResourceBundle.getBundle("messages", locale); + } catch (MissingResourceException e) { + Logger.error("Resource bundle not found for locale: " + locale); + } + } + + private void updateUI() { + setJMenuBar(generateMenuBar()); + gameWindow.updateTitle(); + SwingUtilities.updateComponentTreeUI(this); + } + + private String getMessage(String key) { + String pattern = messages.getString(key); + MessageFormat messageFormat = messageFormatCache.computeIfAbsent(pattern, MessageFormat::new); + return messageFormat.format(new Object[]{}); + } } From f346485eff1b4a8588fff5bc34733ab31bf02f5c Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Wed, 29 May 2024 10:47:59 +0500 Subject: [PATCH 10/12] Update LogWindow.java --- robots/src/gui/LogWindow.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/robots/src/gui/LogWindow.java b/robots/src/gui/LogWindow.java index 723d3e2fc..c9c00da13 100644 --- a/robots/src/gui/LogWindow.java +++ b/robots/src/gui/LogWindow.java @@ -18,7 +18,7 @@ public class LogWindow extends JInternalFrame implements LogChangeListener public LogWindow(LogWindowSource logSource) { - super("Протокол работы", true, true, true, true); + super(MainApplicationFrame.messages.getString("protocol.window.title"), true, true, true, true); m_logSource = logSource; m_logSource.registerListener(this); m_logContent = new TextArea(""); @@ -29,6 +29,7 @@ public LogWindow(LogWindowSource logSource) getContentPane().add(panel); pack(); updateLogContent(); + updateTitleP(); } private void updateLogContent() @@ -40,6 +41,10 @@ private void updateLogContent() } m_logContent.setText(content.toString()); m_logContent.invalidate(); + updateTitleP(); + } + public void updateTitleP() { + setTitle(MainApplicationFrame.messages.getString("protocol.window.title")); } @Override From ddd83b5ae23188548c8bf730e06c82f09a725d4d Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Wed, 29 May 2024 10:48:25 +0500 Subject: [PATCH 11/12] Update GameWindow.java --- robots/src/gui/GameWindow.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index 4c3f8bc06..7475fef39 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -1,19 +1,24 @@ package gui; import java.awt.BorderLayout; +import java.util.ResourceBundle; import javax.swing.JInternalFrame; import javax.swing.JPanel; -public class GameWindow extends JInternalFrame -{ +public class GameWindow extends JInternalFrame { public final GameVisualizer m_visualizer = new GameVisualizer(); - public GameWindow() - { - super("Игровое поле", true, true, true, true); + //private ResourceBundle messages; + + public GameWindow() { + super(MainApplicationFrame.messages.getString("game.window.title"), true, true, true, true); JPanel panel = new JPanel(new BorderLayout()); panel.add(m_visualizer, BorderLayout.CENTER); getContentPane().add(panel); pack(); } + + public void updateTitle() { + setTitle(MainApplicationFrame.messages.getString("game.window.title")); + } } From d49cd9fcbdef2689a0ab8da64e516b6afe2d2a55 Mon Sep 17 00:00:00 2001 From: MikhunS <150695058+MikhunS@users.noreply.github.com> Date: Wed, 29 May 2024 10:49:32 +0500 Subject: [PATCH 12/12] Update RobotCoordinatesWindow.java --- robots/src/gui/RobotCoordinatesWindow.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/robots/src/gui/RobotCoordinatesWindow.java b/robots/src/gui/RobotCoordinatesWindow.java index f728b8315..7a92f841e 100644 --- a/robots/src/gui/RobotCoordinatesWindow.java +++ b/robots/src/gui/RobotCoordinatesWindow.java @@ -7,7 +7,7 @@ public class RobotCoordinatesWindow extends JInternalFrame implements Observer { private final JLabel coordinatesLabel; public RobotCoordinatesWindow(RobotModel model) { - super("Координаты робота"); + super(MainApplicationFrame.messages.getString("robot.coord.window.title")); model.addObserver(this); coordinatesLabel = new JLabel("X: " + model.getX() + ", Y: " + model.getY()); this.add(coordinatesLabel); @@ -15,11 +15,15 @@ public RobotCoordinatesWindow(RobotModel model) { this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); this.setVisible(true); } + public void updateTitle() { + setTitle(MainApplicationFrame.messages.getString("robot.coord.window.title")); + } @Override public void update(Observable o, Object arg) { if (o instanceof RobotModel) { RobotModel model = (RobotModel) o; coordinatesLabel.setText("X: " + model.getX() + ", Y: " + model.getY()); + updateTitle(); } } }