diff --git a/.gitignore b/.gitignore index 2c6eb38..3c86586 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,40 @@ /.metadata /robots/.settings /robots/bin -eclipse.bat \ No newline at end of file +eclipse.bat +out/ +/robots/.classpath +/robots/.project +# Translations +*.mo + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# IDEA +.idea/ +*.iml + +# Maven +target/ \ No newline at end of file diff --git a/robots/src/config/ConfigManager.java b/robots/src/config/ConfigManager.java new file mode 100644 index 0000000..06c5d42 --- /dev/null +++ b/robots/src/config/ConfigManager.java @@ -0,0 +1,96 @@ +package config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; +import log.Logger; + +/** + * Управление сохранением и загрузкой конфигурации приложения. + * Файл хранится в домашнем каталоге пользователя. + */ +public class ConfigManager { + private static final String CONFIG_FILE = System.getProperty("user.home") + File.separator + ".robot-config.properties"; + private final Properties props = new Properties(); + + public void load() { + File file = new File(CONFIG_FILE); + if (!file.exists()) return; + try (FileInputStream fis = new FileInputStream(file)) { + props.load(fis); + } catch (IOException e) { + Logger.debug("Не удалось загрузить конфигурацию: " + e.getMessage()); + } + } + + public void save() { + try (FileOutputStream fos = new FileOutputStream(CONFIG_FILE)) { + props.store(fos, "Robot program configuration"); + } catch (IOException e) { + Logger.debug("Не удалось сохранить конфигурацию: " + e.getMessage()); + } + } + + public void setMainWindowBounds(int x, int y, int width, int height, int state) { + setProperty("main.x", x); + setProperty("main.y", y); + setProperty("main.width", width); + setProperty("main.height", height); + setProperty("main.state", state); + } + + public int getMainWindowX(int defaultValue) { return getIntProperty("main.x", defaultValue); } + public int getMainWindowY(int defaultValue) { return getIntProperty("main.y", defaultValue); } + public int getMainWindowWidth(int defaultValue) { return getIntProperty("main.width", defaultValue); } + public int getMainWindowHeight(int defaultValue) { return getIntProperty("main.height", defaultValue); } + public int getMainWindowState(int defaultValue) { return getIntProperty("main.state", defaultValue); } + + public void setWindowBounds(String windowName, int x, int y, int width, int height, boolean icon, boolean maximized) { + setProperty(key(windowName, "x"), x); + setProperty(key(windowName, "y"), y); + setProperty(key(windowName, "width"), width); + setProperty(key(windowName, "height"), height); + setProperty(key(windowName, "icon"), icon); + setProperty(key(windowName, "maximized"), maximized); + } + + public boolean hasWindow(String windowName) { + return props.containsKey(key(windowName, "x")); + } + + public int getWindowX(String windowName, int defaultValue) { return getIntProperty(key(windowName, "x"), defaultValue); } + public int getWindowY(String windowName, int defaultValue) { return getIntProperty(key(windowName, "y"), defaultValue); } + public int getWindowWidth(String windowName, int defaultValue) { return getIntProperty(key(windowName, "width"), defaultValue); } + public int getWindowHeight(String windowName, int defaultValue) { return getIntProperty(key(windowName, "height"), defaultValue); } + public boolean getWindowIcon(String windowName, boolean defaultValue) { return getBooleanProperty(key(windowName, "icon"), defaultValue); } + public boolean getWindowMaximized(String windowName, boolean defaultValue) { return getBooleanProperty(key(windowName, "maximized"), defaultValue); } + + private String key(String windowName, String suffix) { + return windowName + "." + suffix; // единственное место с конкатенацией (инкапсулировано) + } + + private void setProperty(String key, int value) { + props.setProperty(key, Integer.toString(value)); + } + + private void setProperty(String key, boolean value) { + props.setProperty(key, Boolean.toString(value)); + } + + private int getIntProperty(String key, int defaultValue) { + String value = props.getProperty(key); + if (value == null) return defaultValue; + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + private boolean getBooleanProperty(String key, boolean defaultValue) { + String value = props.getProperty(key); + return value == null ? defaultValue : Boolean.parseBoolean(value); + } +} diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 62e943e..6fecc26 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -2,155 +2,142 @@ import java.awt.Dimension; import java.awt.Toolkit; -import java.awt.event.KeyEvent; - +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import javax.swing.JDesktopPane; import javax.swing.JFrame; import javax.swing.JInternalFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - +import config.ConfigManager; import log.Logger; -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ -public class MainApplicationFrame extends JFrame -{ +public class MainApplicationFrame extends JFrame { + private static final String LOG_WINDOW_TITLE = "Протокол работы"; + private static final String GAME_WINDOW_TITLE = "Игровое поле"; + private static final String LOG_WINDOW_NAME = "LogWindow"; + private static final String GAME_WINDOW_NAME = "GameWindow"; + private final JDesktopPane desktopPane = new JDesktopPane(); - + private final ConfigManager configManager = new ConfigManager(); + public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); + configManager.load(); + loadMainWindowState(); setContentPane(desktopPane); - - + LogWindow logWindow = createLogWindow(); addWindow(logWindow); - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); + gameWindow.setSize(400, 400); addWindow(gameWindow); - setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); + loadInternalWindowState(LOG_WINDOW_NAME, logWindow); + loadInternalWindowState(GAME_WINDOW_NAME, gameWindow); + + setJMenuBar(new MenuBarFactory(this).createMenuBar()); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + exitApplication(); + } + }); + } + + public void exitApplication() { + saveAllWindowsState(); + configManager.save(); + + int result = JOptionPane.showConfirmDialog(this, + "Вы действительно хотите выйти из приложения?", + "Подтверждение выхода", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } } - - 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); } - -// 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 setLookAndFeel(String className) { + try { + UIManager.setLookAndFeel(className); + RobotsProgram.setRussianUIManagerText(); + SwingUtilities.updateComponentTreeUI(this); + } catch (Exception e) { + // ignore } + } - 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); - } + private void loadMainWindowState() { + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int defaultWidth = screenSize.width - inset * 2; + int defaultHeight = screenSize.height - inset * 2; - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; + setBounds(configManager.getMainWindowX(inset), + configManager.getMainWindowY(inset), + configManager.getMainWindowWidth(defaultWidth), + configManager.getMainWindowHeight(defaultHeight)); + setExtendedState(configManager.getMainWindowState(JFrame.NORMAL)); } - - private void setLookAndFeel(String className) - { - try - { - UIManager.setLookAndFeel(className); - SwingUtilities.updateComponentTreeUI(this); + + private void loadInternalWindowState(String windowName, JInternalFrame frame) { + if (!configManager.hasWindow(windowName)) return; + + int x = configManager.getWindowX(windowName, frame.getX()); + int y = configManager.getWindowY(windowName, frame.getY()); + int w = configManager.getWindowWidth(windowName, frame.getWidth()); + int h = configManager.getWindowHeight(windowName, frame.getHeight()); + boolean icon = configManager.getWindowIcon(windowName, false); + boolean maximized = configManager.getWindowMaximized(windowName, false); + + frame.setBounds(x, y, w, h); + try { + if (maximized) { + frame.setMaximum(true); + } else if (icon) { + frame.setIcon(true); + } + } catch (java.beans.PropertyVetoException e) { + Logger.debug("Не удалось восстановить состояние окна " + windowName + ": " + e.getMessage()); } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { - // just ignore + } + + private void saveAllWindowsState() { + configManager.setMainWindowBounds(getX(), getY(), getWidth(), getHeight(), getExtendedState()); + + for (JInternalFrame frame : desktopPane.getAllFrames()) { + String windowName = getWindowNameByTitle(frame.getTitle()); + if (windowName != null) { + configManager.setWindowBounds(windowName, + frame.getX(), frame.getY(), + frame.getWidth(), frame.getHeight(), + frame.isIcon(), frame.isMaximum()); + } } } + + private String getWindowNameByTitle(String title) { + if (LOG_WINDOW_TITLE.equals(title)) return LOG_WINDOW_NAME; + if (GAME_WINDOW_TITLE.equals(title)) return GAME_WINDOW_NAME; + return null; + } } diff --git a/robots/src/gui/MenuBarFactory.java b/robots/src/gui/MenuBarFactory.java new file mode 100644 index 0000000..c286689 --- /dev/null +++ b/robots/src/gui/MenuBarFactory.java @@ -0,0 +1,123 @@ +package gui; + +import java.awt.event.KeyEvent; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.UIManager; +import log.Logger; + +/** + * Фабрика для создания строки меню приложения + */ +public class MenuBarFactory { + + private final MainApplicationFrame frame; + + public MenuBarFactory(MainApplicationFrame frame) { + this.frame = frame; + } + + /** + * Создает главную строку меню + */ + public JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + menuBar.add(createFileMenu()); + menuBar.add(createLookAndFeelMenu()); + menuBar.add(createTestMenu()); + + return menuBar; + } + + /** + * Создает меню "Файл" + */ + private JMenu createFileMenu() { + JMenu fileMenu = new JMenu("Файл"); + fileMenu.setMnemonic(KeyEvent.VK_F); + fileMenu.getAccessibleContext().setAccessibleDescription( + "Управление файлами и приложением"); + + fileMenu.add(createExitMenuItem()); + + return fileMenu; + } + + /** + * Создает пункт меню для выхода из приложения + */ + private JMenuItem createExitMenuItem() { + JMenuItem exitMenuItem = new JMenuItem("Выход", KeyEvent.VK_X); + exitMenuItem.addActionListener((event) -> { + frame.exitApplication(); + }); + return exitMenuItem; + } + + + /** + * Создает меню "Режим отображения" + */ + private JMenu createLookAndFeelMenu() { + JMenu lookAndFeelMenu = new JMenu("Режим отображения"); + lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); + lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( + "Управление режимом отображения приложения"); + + lookAndFeelMenu.add(createSystemLookAndFeelItem()); + lookAndFeelMenu.add(createCrossPlatformLookAndFeelItem()); + + return lookAndFeelMenu; + } + + /** + * Создает пункт меню для системной схемы оформления + */ + private JMenuItem createSystemLookAndFeelItem() { + JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); + systemLookAndFeel.addActionListener((event) -> { + frame.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + frame.invalidate(); + }); + return systemLookAndFeel; + } + + /** + * Создает пункт меню для универсальной схемы оформления + */ + private JMenuItem createCrossPlatformLookAndFeelItem() { + JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); + crossplatformLookAndFeel.addActionListener((event) -> { + frame.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + frame.invalidate(); + }); + return crossplatformLookAndFeel; + } + + /** + * Создает меню "Тесты" + */ + private JMenu createTestMenu() { + JMenu testMenu = new JMenu("Тесты"); + testMenu.setMnemonic(KeyEvent.VK_T); + testMenu.getAccessibleContext().setAccessibleDescription( + "Тестовые команды"); + + testMenu.add(createAddLogMessageItem()); + + return testMenu; + } + + /** + * Создает пункт меню для добавления сообщения в лог + */ + private JMenuItem createAddLogMessageItem() { + JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); + addLogMessageItem.addActionListener((event) -> { + Logger.debug("Новая строка"); + }); + return addLogMessageItem; + } +} diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index ae0930a..468cc39 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -1,25 +1,96 @@ package gui; import java.awt.Frame; - +import java.util.Locale; import javax.swing.SwingUtilities; import javax.swing.UIManager; public class RobotsProgram { public static void main(String[] args) { - try { - UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); + Locale.setDefault(new Locale("ru", "RU")); + setRussianUIManagerText(); + 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); - }); - }} + } catch (Exception e) { + e.printStackTrace(); + } + SwingUtilities.invokeLater(() -> { + MainApplicationFrame frame = new MainApplicationFrame(); + frame.pack(); + frame.setVisible(true); + frame.setExtendedState(Frame.MAXIMIZED_BOTH); + }); + } + + protected static void setRussianUIManagerText() { + // JOptionPane + UIManager.put("OptionPane.yesButtonText", "Да"); + UIManager.put("OptionPane.noButtonText", "Нет"); + UIManager.put("OptionPane.cancelButtonText", "Отмена"); + UIManager.put("OptionPane.okButtonText", "OK"); + UIManager.put("OptionPane.titleText", "Выберите опцию"); + + // JFileChooser + UIManager.put("FileChooser.openButtonText", "Открыть"); + UIManager.put("FileChooser.openButtonToolTipText", "Открыть выбранный файл"); + UIManager.put("FileChooser.saveButtonText", "Сохранить"); + UIManager.put("FileChooser.saveButtonToolTipText", "Сохранить выбранный файл"); + UIManager.put("FileChooser.cancelButtonText", "Отмена"); + UIManager.put("FileChooser.cancelButtonToolTipText", "Отменить выбор"); + UIManager.put("FileChooser.directoryOpenButtonText", "Открыть"); + UIManager.put("FileChooser.directoryOpenButtonToolTipText", "Открыть выбранную папку"); + + UIManager.put("FileChooser.lookInLabelText", "Папка:"); + UIManager.put("FileChooser.fileNameLabelText", "Имя файла:"); + UIManager.put("FileChooser.filesOfTypeLabelText", "Тип файлов:"); + UIManager.put("FileChooser.upFolderToolTipText", "На уровень вверх"); + UIManager.put("FileChooser.homeFolderToolTipText", "Домашняя папка"); + UIManager.put("FileChooser.newFolderToolTipText", "Создать новую папку"); + UIManager.put("FileChooser.listViewButtonToolTipText", "Список"); + UIManager.put("FileChooser.detailsViewButtonToolTipText", "Таблица"); + UIManager.put("FileChooser.fileNameHeaderText", "Имя"); + UIManager.put("FileChooser.fileSizeHeaderText", "Размер"); + UIManager.put("FileChooser.fileTypeHeaderText", "Тип"); + UIManager.put("FileChooser.fileDateHeaderText", "Изменен"); + UIManager.put("FileChooser.fileAttrHeaderText", "Атрибуты"); + + UIManager.put("FileChooser.acceptAllFileFilterText", "Все файлы"); + UIManager.put("FileChooser.newFolderDialogTitle", "Новая папка"); + UIManager.put("FileChooser.newFolderPromptText", "Имя новой папки:"); + + // JColorChooser + UIManager.put("ColorChooser.previewText", "Предпросмотр"); + UIManager.put("ColorChooser.okText", "OK"); + UIManager.put("ColorChooser.cancelText", "Отмена"); + UIManager.put("ColorChooser.resetText", "Сброс"); + UIManager.put("ColorChooser.sampleText", "Образец текста"); + + UIManager.put("ColorChooser.swatchesNameText", "Образцы"); + UIManager.put("ColorChooser.swatchesRecentText", "Последние:"); + UIManager.put("ColorChooser.hsvNameText", "HSV"); + UIManager.put("ColorChooser.hslNameText", "HSL"); + UIManager.put("ColorChooser.rgbNameText", "RGB"); + UIManager.put("ColorChooser.cmykNameText", "CMYK"); + + UIManager.put("ColorChooser.hueText", "Оттенок"); + UIManager.put("ColorChooser.saturationText", "Насыщенность"); + UIManager.put("ColorChooser.valueText", "Значение"); + UIManager.put("ColorChooser.lightnessText", "Освещенность"); + UIManager.put("ColorChooser.redText", "Красный"); + UIManager.put("ColorChooser.greenText", "Зеленый"); + UIManager.put("ColorChooser.blueText", "Синий"); + UIManager.put("ColorChooser.cyanText", "Голубой"); + UIManager.put("ColorChooser.magentaText", "Пурпурный"); + UIManager.put("ColorChooser.yellowText", "Желтый"); + UIManager.put("ColorChooser.blackText", "Черный"); + + // JOptionPane (дополнительные настройки) + UIManager.put("OptionPane.inputDialogTitle", "Ввод данных"); + UIManager.put("OptionPane.messageDialogTitle", "Сообщение"); + UIManager.put("OptionPane.titleText", "Выберите опцию"); + } +}