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 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 62e943e..23a605a 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -13,31 +13,28 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.JOptionPane; +import config.ConfigManager; import log.Logger; -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ +import java.awt.event.WindowEvent; +import java.awt.event.WindowAdapter; + 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); @@ -46,9 +43,16 @@ public MainApplicationFrame() { addWindow(gameWindow); setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + exitApplication(); + } + }); } - + protected LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); @@ -59,87 +63,73 @@ protected LogWindow createLogWindow() Logger.debug("Протокол работает"); return logWindow; } - + protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); + restoreWindowState(frame); } - -// 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(); - + + menuBar.add(createLookAndFeelMenu()); + menuBar.add(createTestMenu()); + 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) { try @@ -153,4 +143,57 @@ private void setLookAndFeel(String className) // just ignore } } + + private final ConfigManager configManager = new ConfigManager(); + private final WindowStateManager windowStateManager = new WindowStateManager(configManager); + + private void saveWindowStates() { + for (JInternalFrame frame : desktopPane.getAllFrames()) { + String windowId = frame.getName(); + + if (windowId != null && !windowId.isEmpty()) { + windowStateManager.saveInternalFrameState(windowId, frame); + } + } + configManager.save(); + } + + + private void exitApplication() { + int n = JOptionPane.showConfirmDialog( + this, + "Вы действительно хотите выйти?", + "Подтверждение выхода", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (n == JOptionPane.YES_OPTION) { + saveWindowStates(); + + for (JInternalFrame frame : desktopPane.getAllFrames()) { + frame.dispose(); + } + + this.dispose(); // Закрываем главное окно + } + } + + private void restoreWindowState(JInternalFrame frame) { + 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/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index ae0930a..570e879 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -10,12 +10,18 @@ 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()); + + // --- ШАГ 5: Глобальная русификация --- + UIManager.put("OptionPane.yesButtonText", "Да"); + UIManager.put("OptionPane.noButtonText", "Нет"); + UIManager.put("OptionPane.cancelButtonText", "Отмена"); + UIManager.put("OptionPane.okButtonText", "Готово"); + // ------------------------------------- + } catch (Exception e) { e.printStackTrace(); } + SwingUtilities.invokeLater(() -> { MainApplicationFrame frame = new MainApplicationFrame(); frame.pack(); 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