diff --git a/robots/src/gui/AppStateManager.java b/robots/src/gui/AppStateManager.java new file mode 100644 index 0000000..1404641 --- /dev/null +++ b/robots/src/gui/AppStateManager.java @@ -0,0 +1,76 @@ +package gui; + +import java.beans.PropertyVetoException; +import javax.swing.JDesktopPane; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import log.Logger; + +public class AppStateManager { + private final WindowConfigManager configManager; + + public AppStateManager() { + this.configManager = new WindowConfigManager(); + } + + public void load() { + configManager.load(); + Logger.debug("Менеджер состояний: загрузка конфигурации запущена"); + } + + public void save() { + configManager.save(); + } + + public void restoreMain(JFrame mainFrame, int defaultX, int defaultY, int defaultW, int defaultH) { + mainFrame.setBounds( + configManager.getInt("main.x", defaultX), + configManager.getInt("main.y", defaultY), + configManager.getInt("main.w", defaultW), + configManager.getInt("main.h", defaultH) + ); + mainFrame.setExtendedState(configManager.getInt("main.state", JFrame.NORMAL)); + } + + public void restoreInternalFrame(JInternalFrame frame, String prefix, int defaultX, int defaultY, int defaultW, int defaultH) { + int w = configManager.getInt(prefix + ".w", defaultW); + int h = configManager.getInt(prefix + ".h", defaultH); + int x = configManager.getInt(prefix + ".x", defaultX); + int y = configManager.getInt(prefix + ".y", defaultY); + + frame.setBounds(x, y, w, h); + + try { + if (configManager.getBool(prefix + ".max", false)) { + frame.setMaximum(true); + } else if (configManager.getBool(prefix + ".icon", false)) { + frame.setIcon(true); + } + } catch (PropertyVetoException e) { + Logger.error("Не удалось восстановить состояние окна '" + prefix + "': " + e.getMessage()); + } + } + + public void saveMain(JFrame mainFrame) { + configManager.saveMain( + mainFrame.getX(), mainFrame.getY(), + mainFrame.getWidth(), mainFrame.getHeight(), + mainFrame.getExtendedState() + ); + } + + public void saveInternalFrame(JInternalFrame frame, String prefix) { + configManager.saveInternal(prefix, + frame.getX(), frame.getY(), + frame.getWidth(), frame.getHeight(), + frame.isIcon(), frame.isMaximum() + ); + } + + public void saveAllFrames(JDesktopPane desktopPane) { + for (JInternalFrame frame : desktopPane.getAllFrames()) { + String prefix = frame.getTitle().replaceAll("\\s+", "_").toLowerCase(); + saveInternalFrame(frame, prefix); + } + } +} \ No newline at end of file diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index f82cfd8..6f9d9a4 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -25,16 +25,17 @@ private static Timer initTimer() private volatile double m_robotPositionX = 100; private volatile double m_robotPositionY = 100; - private volatile double m_robotDirection = 0; + 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; + private static final double maxVelocity = 0.1; //ЛИНЕЙНАЯ + private static final double maxAngularVelocity = 0.001;// УГЛОВАЯ public GameVisualizer() { + m_timer.schedule(new TimerTask() { @Override @@ -98,7 +99,7 @@ protected void onModelUpdateEvent() return; } double velocity = maxVelocity; - double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); + double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY);// поворачиваем если надо налево направо double angularVelocity = 0; if (angleToTarget > m_robotDirection) { diff --git a/robots/src/gui/LogWindow.java b/robots/src/gui/LogWindow.java index 723d3e2..901d8df 100644 --- a/robots/src/gui/LogWindow.java +++ b/robots/src/gui/LogWindow.java @@ -3,10 +3,8 @@ 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; @@ -16,14 +14,14 @@ public class LogWindow extends JInternalFrame 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 +39,10 @@ private void updateLogContent() m_logContent.setText(content.toString()); m_logContent.invalidate(); } - + @Override public void onLogChanged() { EventQueue.invokeLater(this::updateLogContent); } -} +} \ No newline at end of file diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 62e943e..d2aa0fc 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -1,156 +1,98 @@ 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 javax.swing.*; import log.Logger; -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ -public class MainApplicationFrame extends JFrame -{ +public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); - + private LogWindow logWindow; + private GameWindow gameWindow; + private final AppStateManager stateManager = new AppStateManager(); + + private static final int DEFAULT_INSET = 50; + private static final int DEFAULT_INTERNAL_WIDTH = 600; + private static final int DEFAULT_INTERNAL_HEIGHT = 400; + private static final int DEFAULT_GAME_X = 10; + private static final int DEFAULT_GAME_Y = 10; + private static final int DEFAULT_LOG_X = 220; + private static final int DEFAULT_LOG_Y = 10; + public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; + stateManager.load(); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); + int defW = screenSize.width - DEFAULT_INSET * 2; + int defH = screenSize.height - DEFAULT_INSET * 2; + + stateManager.restoreMain(this, DEFAULT_INSET, DEFAULT_INSET, defW, defH); setContentPane(desktopPane); - - - LogWindow logWindow = createLogWindow(); + + logWindow = createLogWindow(); + stateManager.restoreInternalFrame(logWindow, "log", DEFAULT_LOG_X, DEFAULT_LOG_Y, DEFAULT_INTERNAL_WIDTH, DEFAULT_INTERNAL_HEIGHT); addWindow(logWindow); - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); + gameWindow = new GameWindow(); + stateManager.restoreInternalFrame(gameWindow, "game", DEFAULT_GAME_X, DEFAULT_GAME_Y, DEFAULT_INTERNAL_WIDTH, DEFAULT_INTERNAL_HEIGHT); addWindow(gameWindow); setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent e) { + exitApplication(); + } + }); + + Logger.debug("Главное окно инициализировано"); } - - protected LogWindow createLogWindow() - { - LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); - logWindow.setLocation(10,10); - logWindow.setSize(300, 800); - setMinimumSize(logWindow.getSize()); - logWindow.pack(); + + protected LogWindow createLogWindow() { + LogWindow lw = new LogWindow(Logger.getDefaultLogSource()); Logger.debug("Протокол работает"); - return logWindow; + return lw; } - - protected void addWindow(JInternalFrame frame) - { + + 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); - } + public void exitApplication() { + stateManager.saveMain(this); + stateManager.saveAllFrames(desktopPane); + stateManager.save(); - 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); + Logger.debug("Конфигурация сохранена перед выходом"); + + UIManager.put("OptionPane.yesButtonText", "Да"); + UIManager.put("OptionPane.noButtonText", "Нет"); + int result = JOptionPane.showConfirmDialog( + this, + "Вы действительно хотите выйти?", + "Подтверждение выхода", + JOptionPane.YES_NO_OPTION + ); + if (result == JOptionPane.YES_OPTION) { + Logger.debug("Приложение закрыто пользователем"); + System.exit(0); } + } - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; + private JMenuBar generateMenuBar() { + return new MenuBarBuilder(this).buildMenuBar(); } - - private void setLookAndFeel(String className) - { - try - { + + public void setLookAndFeel(String className) { + try { UIManager.setLookAndFeel(className); SwingUtilities.updateComponentTreeUI(this); - } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { - // just ignore + Logger.debug("Тема оформления изменена: " + className); + } catch (Exception e) { + Logger.error("Не удалось установить тему оформления: " + e.getMessage()); } } -} +} \ No newline at end of file diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index ae0930a..05896da 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -1,25 +1,19 @@ package gui; -import java.awt.Frame; - import javax.swing.SwingUtilities; import javax.swing.UIManager; -public class RobotsProgram -{ +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(); - } - SwingUtilities.invokeLater(() -> { - MainApplicationFrame frame = new MainApplicationFrame(); - frame.pack(); - frame.setVisible(true); - frame.setExtendedState(Frame.MAXIMIZED_BOTH); - }); - }} + try { + UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); + } catch (Exception e) { + e.printStackTrace(); + } + + SwingUtilities.invokeLater(() -> { + MainApplicationFrame frame = new MainApplicationFrame(); + frame.setVisible(true); + }); + } +} \ No newline at end of file diff --git a/robots/src/gui/WindowConfigManager.java b/robots/src/gui/WindowConfigManager.java new file mode 100644 index 0000000..3cb7a07 --- /dev/null +++ b/robots/src/gui/WindowConfigManager.java @@ -0,0 +1,69 @@ +package gui; +import java.io.*; +import java.util.Properties; +import log.Logger; + +public class WindowConfigManager { + private static final String FILE_NAME = ".game_window_config.properties"; + private final Properties props = new Properties(); + private final File file; + + public WindowConfigManager() { + file = new File(System.getProperty("user.home"), FILE_NAME); + + } + + public void load() { + if (file.exists()) { + try (FileInputStream fis = new FileInputStream(file)) { + props.load(fis); + Logger.debug("Конфигурация загружена успешно"); + } catch (IOException e) { + Logger.error("Не удалось загрузить конфигурацию: " + e.getMessage()); + } + } else { + Logger.debug("Файл конфигурации не найден, используются значения по умолчанию"); + } + } + + public void save() { + try (FileOutputStream fos = new FileOutputStream(file)) { + props.store(fos, "Application Window Configuration"); + + } catch (IOException e) { + Logger.error("Не удалось сохранить конфигурацию: " + e.getMessage()); + } + } + + public int getInt(String key, int def) { + String val = props.getProperty(key); + try { + return val != null ? Integer.parseInt(val) : def; + } catch (NumberFormatException e) { + Logger.error("Неверный формат числа для ключа '" + key + "': " + val); + return def; + } + } + + public boolean getBool(String key, boolean def) { + String val = props.getProperty(key); + return val != null ? Boolean.parseBoolean(val) : def; + } + + public void saveMain(int x, int y, int w, int h, int state) { + props.setProperty("main.x", String.valueOf(x)); + props.setProperty("main.y", String.valueOf(y)); + props.setProperty("main.w", String.valueOf(w)); + props.setProperty("main.h", String.valueOf(h)); + props.setProperty("main.state", String.valueOf(state)); + } + + public void saveInternal(String prefix, int x, int y, int w, int h, boolean iconified, boolean maximized) { + props.setProperty(prefix + ".x", String.valueOf(x)); + props.setProperty(prefix + ".y", String.valueOf(y)); + props.setProperty(prefix + ".w", String.valueOf(w)); + props.setProperty(prefix + ".h", String.valueOf(h)); + props.setProperty(prefix + ".icon", String.valueOf(iconified)); + props.setProperty(prefix + ".max", String.valueOf(maximized)); + } +} \ No newline at end of file