From 0fee413910997ed162eca5f18fe792980e2484e6 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Wed, 15 Apr 2026 14:04:28 +0500 Subject: [PATCH 01/14] Task-1: Add centralized exitApplication logic and set DO_NOTHING_ON_CLOSE --- robots/src/gui/MainApplicationFrame.java | 48 +++++++++--------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 62e943e..01c4db2 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -46,7 +46,7 @@ public MainApplicationFrame() { addWindow(gameWindow); setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); } protected LogWindow createLogWindow() @@ -66,35 +66,6 @@ protected void addWindow(JInternalFrame 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(); @@ -153,4 +124,21 @@ private void setLookAndFeel(String className) // just ignore } } + + private void exitApplication() { + Object[] options = { "Да", "Нет" }; + int n = JOptionPane.showOptionDialog(this, + "Вы действительно хотите выйти?", + "Подтверждение выхода", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + if (n == JOptionPane.YES_OPTION) { + // В будущем здесь добавится вызов метода сохранения состояния окон + this.dispose(); + System.exit(0); + } + } } From efe3641d31b67f2fc670778e7c5a52f0d9129069 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Wed, 15 Apr 2026 14:20:02 +0500 Subject: [PATCH 02/14] Task-1: Connect exit logic to WindowListener --- robots/src/gui/MainApplicationFrame.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 01c4db2..aea2370 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -16,6 +16,9 @@ import log.Logger; +import java.awt.event.WindowEvent +import java.awt.event.WindowAdapter + /** * Что требуется сделать: * 1. Метод создания меню перегружен функционалом и трудно читается. @@ -47,6 +50,13 @@ public MainApplicationFrame() { setJMenuBar(generateMenuBar()); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + exitApplication(); + } + }); } protected LogWindow createLogWindow() From 74d769af0abe41da5eb298f5f66beb3b9564e78b Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Wed, 15 Apr 2026 14:44:04 +0500 Subject: [PATCH 03/14] Task-1: Refactor generateMenuBar and add "Exit" menu item --- robots/src/gui/MainApplicationFrame.java | 80 ++++++++++++++---------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index aea2370..6704d9c 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -79,46 +79,60 @@ protected void addWindow(JInternalFrame frame) private JMenuBar generateMenuBar() { JMenuBar menuBar = new JMenuBar(); - + + menuBar.add(lookAndFeelMenu); + menuBar.add(testMenu); + menuBar.add(createActionMenu()); // Добавляем наше новое меню + + return menuBar; + } + + private JMenu createLookAndFeelMenu() { 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); - } + JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); + systemLookAndFeel.addActionListener((event) -> { + setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + this.invalidate(); + }); + lookAndFeelMenu.add(systemLookAndFeel); + + JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_U); + crossplatformLookAndFeel.addActionListener((event) -> { + setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + this.invalidate(); + }); + lookAndFeelMenu.add(crossplatformLookAndFeel); + return lookAndFeelMenu; + } + + private JMenu createTestMenu() { 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; + JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); + addLogMessageItem.addActionListener((event) -> { + Logger.debug("Новая строка"); + }); + testMenu.add(addLogMessageItem); + + return testMenu; + } + + private JMenu createActionMenu() { + JMenu actionMenu = new JMenu("Управление"); + actionMenu.setMnemonic(KeyEvent.VK_A); + + JMenuItem exitItem = new JMenuItem("Выход", KeyEvent.VK_X); + exitItem.addActionListener((event) -> { + // Вызываем нашу готовую логику из Шага №2 + exitApplication(); + }); + actionMenu.add(exitItem); + + return actionMenu; } private void setLookAndFeel(String className) From 88c5b68ea2a32255f78b8916a3ff0ab2d03abf2c Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Thu, 16 Apr 2026 13:11:29 +0500 Subject: [PATCH 04/14] Task-1: Implement global UI localization using UIManager --- robots/src/gui/RobotsProgram.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index ae0930a..2563a61 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -10,12 +10,20 @@ public class RobotsProgram public static void main(String[] args) { try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); + + // --- ШАГ 5: Глобальная русификация --- + UIManager.put("OptionPane.yesButtonText", "Да"); + UIManager.put("OptionPane.noButtonText", "Нет"); + UIManager.put("OptionPane.cancelButtonText", "Отмена"); + UIManager.put("OptionPane.okButtonText", "Готово"); + // ------------------------------------- // UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } + SwingUtilities.invokeLater(() -> { MainApplicationFrame frame = new MainApplicationFrame(); frame.pack(); From e2d985e583ae9132cdbdbf62462fd3ed6f7b2c69 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Thu, 16 Apr 2026 13:12:17 +0500 Subject: [PATCH 05/14] Refactor: Remove dead LookAndFeel code in RobotsProgram --- robots/src/gui/RobotsProgram.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index 2563a61..570e879 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -17,9 +17,7 @@ public static void main(String[] args) { UIManager.put("OptionPane.cancelButtonText", "Отмена"); UIManager.put("OptionPane.okButtonText", "Готово"); // ------------------------------------- -// UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); -// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); -// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + } catch (Exception e) { e.printStackTrace(); } From d57ae1a6ee9ddde85a8fd278e10b51fed342fa32 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Sat, 18 Apr 2026 19:23:30 +0500 Subject: [PATCH 06/14] Fix: Resolve compilation errors and adjust project SDK version --- robots/src/gui/MainApplicationFrame.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 6704d9c..cd1f2f9 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -13,11 +13,12 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.JOptionPane; import log.Logger; -import java.awt.event.WindowEvent -import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent; +import java.awt.event.WindowAdapter; /** * Что требуется сделать: @@ -80,8 +81,8 @@ private JMenuBar generateMenuBar() { JMenuBar menuBar = new JMenuBar(); - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); + menuBar.add(createLookAndFeelMenu()); + menuBar.add(createTestMenu()); menuBar.add(createActionMenu()); // Добавляем наше новое меню return menuBar; From 7c78de1e89f49cba5f43a6d3c207c63bb1c09139 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Tue, 21 Apr 2026 18:23:28 +0500 Subject: [PATCH 07/14] Docs: remove obsolete TODO instructions and dead comments --- robots/src/gui/MainApplicationFrame.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index cd1f2f9..d50083b 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -20,12 +20,6 @@ import java.awt.event.WindowEvent; import java.awt.event.WindowAdapter; -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); From 4544473b676bf142987d28338c435cd588566518 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Tue, 21 Apr 2026 18:46:07 +0500 Subject: [PATCH 08/14] Refactor: implement graceful shutdown by stopping timers and removing System.exit, removing redundant options --- robots/src/gui/GameVisualizer.java | 5 +++++ robots/src/gui/GameWindow.java | 9 ++++++++- robots/src/gui/MainApplicationFrame.java | 19 ++++++++++--------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index f82cfd8..6f8c6a4 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -22,6 +22,11 @@ private static Timer initTimer() Timer timer = new Timer("events generator", true); return timer; } + + public void stopTimer() { + m_timer.cancel(); + m_timer.purge(); + } private volatile double m_robotPositionX = 100; private volatile double m_robotPositionY = 100; diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index ecb63c0..1a66354 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -8,7 +8,14 @@ public class GameWindow extends JInternalFrame { private final GameVisualizer m_visualizer; - public GameWindow() + + @Override + public void dispose() { + m_visualizer.stopTimer(); + super.dispose(); + } + + public GameWindow() { super("Игровое поле", true, true, true, true); m_visualizer = new GameVisualizer(); diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index d50083b..7c0cb51 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -145,19 +145,20 @@ private void setLookAndFeel(String className) } private void exitApplication() { - Object[] options = { "Да", "Нет" }; - int n = JOptionPane.showOptionDialog(this, + int n = JOptionPane.showConfirmDialog( + this, "Вы действительно хотите выйти?", "Подтверждение выхода", JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); + JOptionPane.QUESTION_MESSAGE + ); + if (n == JOptionPane.YES_OPTION) { - // В будущем здесь добавится вызов метода сохранения состояния окон - this.dispose(); - System.exit(0); + // Проходим по всем JInternalFrame на панели + for (JInternalFrame frame : desktopPane.getAllFrames()) { + frame.dispose(); // Это вызовет метод dispose у GameWindow и остановит таймер + } + this.dispose(); // Закрываем главное окно } } } From ce3078414e001197850bb011f6929ae750eafc61 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Tue, 21 Apr 2026 19:14:36 +0500 Subject: [PATCH 09/14] Task-2: add WindowConfigManager for handling application properties --- robots/src/gui/WindowConfigManager.java | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 robots/src/gui/WindowConfigManager.java diff --git a/robots/src/gui/WindowConfigManager.java b/robots/src/gui/WindowConfigManager.java new file mode 100644 index 0000000..f9832ba --- /dev/null +++ b/robots/src/gui/WindowConfigManager.java @@ -0,0 +1,44 @@ +package gui; + +import java.io.*; +import java.util.Properties; + +public class WindowConfigManager { + // Файл будет храниться в домашней папке пользователя: /Users/имя/.robots_windows.properties + private static final File CONFIG_FILE = new File(System.getProperty("user.home"), ".robots_windows.properties"); + private final Properties properties = new Properties(); + + public WindowConfigManager() { + load(); + } + + // Загрузка данных из файла в память + private void load() { + if (CONFIG_FILE.exists()) { + try (InputStream is = new FileInputStream(CONFIG_FILE)) { + properties.load(is); + } catch (IOException e) { + System.err.println("Ошибка при загрузке конфига: " + e.getMessage()); + } + } + } + + // Сохранение данных из памяти в файл + public void save() { + try (OutputStream os = new FileOutputStream(CONFIG_FILE)) { + properties.store(os, "Window States Configuration"); + } catch (IOException e) { + System.err.println("Ошибка при сохранении конфига: " + e.getMessage()); + } + } + + // Установка значения + public void setProperty(String key, String value) { + properties.setProperty(key, value); + } + + // Получение значения + public String getProperty(String key) { + return properties.getProperty(key); + } +} \ No newline at end of file From e161ac6b412b13941d93e2c00d523d2612f241e9 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Tue, 21 Apr 2026 19:22:24 +0500 Subject: [PATCH 10/14] Tasl-2: implement saveWindowStates logic using WindowConfigManager --- robots/src/gui/MainApplicationFrame.java | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 7c0cb51..dd749e4 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -144,6 +144,23 @@ private void setLookAndFeel(String className) } } + private final WindowConfigManager configManager = new WindowConfigManager(); + + private void saveWindowStates() { + for (JInternalFrame frame : desktopPane.getAllFrames()) { + // префикс для ключей + String prefix = frame.getTitle() + "."; + + configManager.setProperty(prefix + "x", String.valueOf(frame.getX())); + configManager.setProperty(prefix + "y", String.valueOf(frame.getY())); + configManager.setProperty(prefix + "width", String.valueOf(frame.getWidth())); + configManager.setProperty(prefix + "height", String.valueOf(frame.getHeight())); + configManager.setProperty(prefix + "isIcon", String.valueOf(frame.isIcon())); // Свернуто + configManager.setProperty(prefix + "isMaximum", String.valueOf(frame.isMaximum())); // Развернуто + } + configManager.save(); + } + private void exitApplication() { int n = JOptionPane.showConfirmDialog( this, @@ -154,10 +171,12 @@ private void exitApplication() { ); if (n == JOptionPane.YES_OPTION) { - // Проходим по всем JInternalFrame на панели + saveWindowStates(); + for (JInternalFrame frame : desktopPane.getAllFrames()) { frame.dispose(); // Это вызовет метод dispose у GameWindow и остановит таймер } + this.dispose(); // Закрываем главное окно } } From 92852f140bcab1d7c8d2a94c14d56547ec0854f0 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Mon, 4 May 2026 14:56:04 +0500 Subject: [PATCH 11/14] complete Task-2: window position and size saving, resolve window restoration recursion and finalize task 2 --- robots/src/gui/MainApplicationFrame.java | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index dd749e4..7f6c34a 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -69,6 +69,7 @@ protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); + restoreWindowState(frame); } private JMenuBar generateMenuBar() @@ -180,4 +181,46 @@ private void exitApplication() { this.dispose(); // Закрываем главное окно } } + + private void restoreWindowState(JInternalFrame frame) { + String prefix = frame.getTitle() + "."; + + // 1. Читаем данные заранее (вне потока отрисовки) + String xStr = configManager.getProperty(prefix + "x"); + String yStr = configManager.getProperty(prefix + "y"); + String wStr = configManager.getProperty(prefix + "width"); + String hStr = configManager.getProperty(prefix + "height"); + String isIconStr = configManager.getProperty(prefix + "isIcon"); + String isMaxStr = configManager.getProperty(prefix + "isMaximum"); + + // Если данных в файле нет, ничего не делаем и выходим + if (xStr == null || yStr == null) return; + + // 2. Используем invokeLater, чтобы дождаться полной инициализации GUI + SwingUtilities.invokeLater(() -> { + try { + // Устанавливаем границы окна + frame.setBounds( + Integer.parseInt(xStr), + Integer.parseInt(yStr), + Integer.parseInt(wStr), + Integer.parseInt(hStr) + ); + + // Состояния сворачивания/разворачивания делаем через еще один вложенный цикл + // Это гарантирует, что Swing уже "знает" финальный размер окна + SwingUtilities.invokeLater(() -> { + try { + if (Boolean.parseBoolean(isMaxStr)) frame.setMaximum(true); + if (Boolean.parseBoolean(isIconStr)) frame.setIcon(true); + } catch (Exception e) { + // Игнорируем вето на изменение состояния + } + }); + } catch (Exception e) { + System.err.println("Ошибка восстановления окна " + frame.getTitle()); + } + }); + } + } From f49ce95ed9e2faa09da0600c98db0a50cf5a08b0 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Sun, 31 May 2026 22:09:46 +0500 Subject: [PATCH 12/14] refactor: create universal ConfigManager in a separate package --- robots/src/config/ConfigManager.java | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 robots/src/config/ConfigManager.java diff --git a/robots/src/config/ConfigManager.java b/robots/src/config/ConfigManager.java new file mode 100644 index 0000000..ae4fd6e --- /dev/null +++ b/robots/src/config/ConfigManager.java @@ -0,0 +1,65 @@ +package config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Универсальный менеджер конфигурации. + * Отвечает только за сохранение и загрузку пар "ключ-значение" в файл. + * Никак не связан с GUI. + */ +public class ConfigManager { + // Имя файла настроек, который будет создаваться в корне проекта + private static final String CONFIG_FILE_NAME = "window_settings.properties"; + private final Properties properties; + private final File configFile; + + public ConfigManager() { + this.properties = new Properties(); + // Находим домашнюю директорию пользователя, чтобы сохранять конфиг туда + String userHome = System.getProperty("user.home"); + this.configFile = new File(userHome, CONFIG_FILE_NAME); + load(); + } + + /** + * Возвращает значение по ключу. Если ключа нет, вернет defaultValue. + */ + public String getValue(String key, String defaultValue) { + return properties.getProperty(key, defaultValue); + } + + /** + * Устанавливает значение для ключа. + */ + public void setValue(String key, String value) { + properties.setProperty(key, value); + } + + /** + * Загружает настройки из файла. + */ + private void load() { + if (configFile.exists()) { + try (FileInputStream in = new FileInputStream(configFile)) { + properties.load(in); + } catch (IOException e) { + System.err.println("Ошибка при загрузке файла конфигурации: " + e.getMessage()); + } + } + } + + /** + * Сохраняет текущие настройки в файл на диск. + */ + public void save() { + try (FileOutputStream out = new FileOutputStream(configFile)) { + properties.store(out, "Application Window Settings"); + } catch (IOException e) { + System.err.println("Ошибка при сохранении файла конфигурации: " + e.getMessage()); + } + } +} \ No newline at end of file From 1edc79da5263a2a4a3d539ceefefea792d10ee98 Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Sun, 31 May 2026 23:08:45 +0500 Subject: [PATCH 13/14] refactor: introduce WindowStateManager for handling frame states --- robots/src/gui/WindowStateManager.java | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 robots/src/gui/WindowStateManager.java diff --git a/robots/src/gui/WindowStateManager.java b/robots/src/gui/WindowStateManager.java new file mode 100644 index 0000000..b269e2d --- /dev/null +++ b/robots/src/gui/WindowStateManager.java @@ -0,0 +1,57 @@ +package gui; + +import javax.swing.JInternalFrame; +import config.ConfigManager; + +/** + * Класс для сохранения и восстановления состояний окон. + * Реализует разделение логики работы с окнами и работы с файлами. + */ +public class WindowStateManager { + private final ConfigManager configManager; + + public WindowStateManager(ConfigManager configManager) { + this.configManager = configManager; + } + + /** + * Сохраняет состояние конкретного окна. + * @param windowId Уникальный неизменяемый идентификатор окна (например, "log_window") + * @param window Само окно JInternalFrame + */ + public void saveInternalFrameState(String windowId, JInternalFrame window) { + configManager.setValue(windowId + ".x", String.valueOf(window.getX())); + configManager.setValue(windowId + ".y", String.valueOf(window.getY())); + configManager.setValue(windowId + ".width", String.valueOf(window.getWidth())); + configManager.setValue(windowId + ".height", String.valueOf(window.getHeight())); + configManager.setValue(windowId + ".isIcon", String.valueOf(window.isIcon())); + configManager.setValue(windowId + ".isMaximum", String.valueOf(window.isMaximum())); + } + + /** + * Восстанавливает состояние конкретного окна. + * @param windowId Уникальный неизменяемый идентификатор окна + * @param window Само окно JInternalFrame + * @param defaultX Координаты по умолчанию, если программа запускается впервые + */ + /** + * Восстанавливает состояние конкретного окна. + */ + public void restoreInternalFrameState(String windowId, JInternalFrame window, int defaultX, int defaultY, int defaultWidth, int defaultHeight) { + int x = Integer.parseInt(configManager.getValue(windowId + ".x", String.valueOf(defaultX))); + int y = Integer.parseInt(configManager.getValue(windowId + ".y", String.valueOf(defaultY))); + int width = Integer.parseInt(configManager.getValue(windowId + ".width", String.valueOf(defaultWidth))); + int height = Integer.parseInt(configManager.getValue(windowId + ".height", String.valueOf(defaultHeight))); + boolean isIcon = Boolean.parseBoolean(configManager.getValue(windowId + ".isIcon", "false")); + boolean isMaximum = Boolean.parseBoolean(configManager.getValue(windowId + ".isMaximum", "false")); + + + window.setBounds(x, y, width, height); + try { + window.setIcon(isIcon); + window.setMaximum(isMaximum); + } catch (Exception e) { + System.err.println("Не удалось восстановить состояние свертывания/максимизации для " + windowId); + } + } +} \ No newline at end of file From 3612749dc86e2f77f27a504b91747f5c9181b24e Mon Sep 17 00:00:00 2001 From: senyaa77 Date: Sun, 31 May 2026 23:09:56 +0500 Subject: [PATCH 14/14] refactor: integrate WindowStateManager in MainApplicationFrame, delete WindowConfigManager --- robots/src/gui/MainApplicationFrame.java | 95 +++++++++--------------- robots/src/gui/WindowConfigManager.java | 44 ----------- 2 files changed, 34 insertions(+), 105 deletions(-) delete mode 100644 robots/src/gui/WindowConfigManager.java diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 7f6c34a..23a605a 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -15,6 +15,7 @@ import javax.swing.UnsupportedLookAndFeelException; import javax.swing.JOptionPane; +import config.ConfigManager; import log.Logger; import java.awt.event.WindowEvent; @@ -23,19 +24,17 @@ 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); @@ -53,7 +52,7 @@ public void windowClosing(WindowEvent e) { } }); } - + protected LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); @@ -64,21 +63,21 @@ protected LogWindow createLogWindow() Logger.debug("Протокол работает"); return logWindow; } - + protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); restoreWindowState(frame); } - + private JMenuBar generateMenuBar() { JMenuBar menuBar = new JMenuBar(); menuBar.add(createLookAndFeelMenu()); menuBar.add(createTestMenu()); - menuBar.add(createActionMenu()); // Добавляем наше новое меню + menuBar.add(createActionMenu()); return menuBar; } @@ -130,7 +129,7 @@ private JMenu createActionMenu() { return actionMenu; } - + private void setLookAndFeel(String className) { try @@ -145,23 +144,21 @@ private void setLookAndFeel(String className) } } - private final WindowConfigManager configManager = new WindowConfigManager(); + private final ConfigManager configManager = new ConfigManager(); + private final WindowStateManager windowStateManager = new WindowStateManager(configManager); private void saveWindowStates() { for (JInternalFrame frame : desktopPane.getAllFrames()) { - // префикс для ключей - String prefix = frame.getTitle() + "."; - - configManager.setProperty(prefix + "x", String.valueOf(frame.getX())); - configManager.setProperty(prefix + "y", String.valueOf(frame.getY())); - configManager.setProperty(prefix + "width", String.valueOf(frame.getWidth())); - configManager.setProperty(prefix + "height", String.valueOf(frame.getHeight())); - configManager.setProperty(prefix + "isIcon", String.valueOf(frame.isIcon())); // Свернуто - configManager.setProperty(prefix + "isMaximum", String.valueOf(frame.isMaximum())); // Развернуто + String windowId = frame.getName(); + + if (windowId != null && !windowId.isEmpty()) { + windowStateManager.saveInternalFrameState(windowId, frame); + } } configManager.save(); } + private void exitApplication() { int n = JOptionPane.showConfirmDialog( this, @@ -175,7 +172,7 @@ private void exitApplication() { saveWindowStates(); for (JInternalFrame frame : desktopPane.getAllFrames()) { - frame.dispose(); // Это вызовет метод dispose у GameWindow и остановит таймер + frame.dispose(); } this.dispose(); // Закрываем главное окно @@ -183,44 +180,20 @@ private void exitApplication() { } private void restoreWindowState(JInternalFrame frame) { - String prefix = frame.getTitle() + "."; - - // 1. Читаем данные заранее (вне потока отрисовки) - String xStr = configManager.getProperty(prefix + "x"); - String yStr = configManager.getProperty(prefix + "y"); - String wStr = configManager.getProperty(prefix + "width"); - String hStr = configManager.getProperty(prefix + "height"); - String isIconStr = configManager.getProperty(prefix + "isIcon"); - String isMaxStr = configManager.getProperty(prefix + "isMaximum"); - - // Если данных в файле нет, ничего не делаем и выходим - if (xStr == null || yStr == null) return; - - // 2. Используем invokeLater, чтобы дождаться полной инициализации GUI - SwingUtilities.invokeLater(() -> { - try { - // Устанавливаем границы окна - frame.setBounds( - Integer.parseInt(xStr), - Integer.parseInt(yStr), - Integer.parseInt(wStr), - Integer.parseInt(hStr) - ); - - // Состояния сворачивания/разворачивания делаем через еще один вложенный цикл - // Это гарантирует, что Swing уже "знает" финальный размер окна - SwingUtilities.invokeLater(() -> { - try { - if (Boolean.parseBoolean(isMaxStr)) frame.setMaximum(true); - if (Boolean.parseBoolean(isIconStr)) frame.setIcon(true); - } catch (Exception e) { - // Игнорируем вето на изменение состояния - } - }); - } catch (Exception e) { - System.err.println("Ошибка восстановления окна " + frame.getTitle()); - } - }); - } + String windowId = frame.getName(); + + if (windowId == null || windowId.isEmpty()) { + return; + } + int defaultX = 10, defaultY = 10, defaultWidth = 400, defaultHeight = 400; + + if ("log_window".equals(windowId)) { + defaultX = 10; defaultY = 10; defaultWidth = 300; defaultHeight = 800; + } else if ("game_window".equals(windowId)) { + defaultX = 320; defaultY = 10; defaultWidth = 400; defaultHeight = 400; + } + + windowStateManager.restoreInternalFrameState(windowId, frame, defaultX, defaultY, defaultWidth, defaultHeight); + } } diff --git a/robots/src/gui/WindowConfigManager.java b/robots/src/gui/WindowConfigManager.java deleted file mode 100644 index f9832ba..0000000 --- a/robots/src/gui/WindowConfigManager.java +++ /dev/null @@ -1,44 +0,0 @@ -package gui; - -import java.io.*; -import java.util.Properties; - -public class WindowConfigManager { - // Файл будет храниться в домашней папке пользователя: /Users/имя/.robots_windows.properties - private static final File CONFIG_FILE = new File(System.getProperty("user.home"), ".robots_windows.properties"); - private final Properties properties = new Properties(); - - public WindowConfigManager() { - load(); - } - - // Загрузка данных из файла в память - private void load() { - if (CONFIG_FILE.exists()) { - try (InputStream is = new FileInputStream(CONFIG_FILE)) { - properties.load(is); - } catch (IOException e) { - System.err.println("Ошибка при загрузке конфига: " + e.getMessage()); - } - } - } - - // Сохранение данных из памяти в файл - public void save() { - try (OutputStream os = new FileOutputStream(CONFIG_FILE)) { - properties.store(os, "Window States Configuration"); - } catch (IOException e) { - System.err.println("Ошибка при сохранении конфига: " + e.getMessage()); - } - } - - // Установка значения - public void setProperty(String key, String value) { - properties.setProperty(key, value); - } - - // Получение значения - public String getProperty(String key) { - return properties.getProperty(key); - } -} \ No newline at end of file