Add support for multiple tile sets master
authorJakob Cornell <jakob+gpg@jcornell.net>
Thu, 4 Feb 2021 01:26:20 +0000 (19:26 -0600)
committerJakob Cornell <jakob+gpg@jcornell.net>
Thu, 4 Feb 2021 01:26:20 +0000 (19:26 -0600)
This is a breaking change in the save format; the application discards previously saved data if it's
incompatible.

src/net/jcornell/tile_draw/Main.java
src/net/jcornell/tile_draw/SaveData.java
src/net/jcornell/tile_draw/TileConfigModel.java
src/net/jcornell/tile_draw/TileSelectController.java [deleted file]
src/net/jcornell/tile_draw/TileSetEditController.java [new file with mode: 0644]
src/net/jcornell/tile_draw/TileSetsController.java [new file with mode: 0644]
src/net/jcornell/tile_draw/util/Util.java

index c9bbf565b33a9f65b6818051d57fb171f1ce2fe6..98cf8c75d010e287845d41aa7267f0fa835c9c4f 100644 (file)
@@ -1,10 +1,12 @@
 package net.jcornell.tile_draw;
 
 package net.jcornell.tile_draw;
 
+import javax.swing.JFrame;
 import java.awt.Window;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.awt.Window;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.awt.Dimension;
 
 import net.jcornell.tile_draw.util.Util;
 
 
 import net.jcornell.tile_draw.util.Util;
 
@@ -16,20 +18,16 @@ public class Main {
                        data = Util.loadData();
                } catch (IOException | ClassNotFoundException | Util.ImageLoadException e) {
                        e.printStackTrace();
                        data = Util.loadData();
                } catch (IOException | ClassNotFoundException | Util.ImageLoadException e) {
                        e.printStackTrace();
+                       data = new SaveData(new ArrayList<>(), null);
                }
 
                }
 
-               List<TileConfigModel> tileModels;
-               File chooserDir;
-               if (data == null) {
-                       tileModels = new ArrayList<>();
-                       chooserDir = null;
-               } else {
-                       tileModels = data.tileModels;
-                       chooserDir = data.fileChooserDir;
-               }
-
-               TileSelectController tsController = new TileSelectController(tileModels, chooserDir);
-               Window window = tsController.view.window;
+               JFrame window = new JFrame("Tile Draw");
+               window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               TileSetsController controller = new TileSetsController(data.tileSets, data.fileChooserDir);
+               TileSetsSwingView view = new TileSetsSwingView(controller);
+               controller.view = view;
+               window.setContentPane(view.contentPane);
+               window.pack();
                window.setLocationRelativeTo(null);
                window.setVisible(true);
        }
                window.setLocationRelativeTo(null);
                window.setVisible(true);
        }
index 79b3e2716274170f1be3fb15c7c6baf02aa39031..2b077c539ac73edb1a1ed19acfdc7c86343313a0 100644 (file)
@@ -4,17 +4,19 @@ import java.io.Serializable;
 import java.io.File;
 import java.util.List;
 
 import java.io.File;
 import java.util.List;
 
+import net.jcornell.tile_draw.TileSetsController.TileSet;
+
 
 public class SaveData implements Serializable {
 
 public class SaveData implements Serializable {
-       private static final long serialVersionUID = 0;
+       private static final long serialVersionUID = 1;
 
 
-       public List<TileConfigModel> tileModels;
+       public List<TileSet> tileSets;
        public File fileChooserDir;
 
        public SaveData() {}
 
        public File fileChooserDir;
 
        public SaveData() {}
 
-       public SaveData(List<TileConfigModel> tileModels, File fileChooserDir) {
-               this.tileModels = tileModels;
+       public SaveData(List<TileSet> tileSets, File fileChooserDir) {
+               this.tileSets = tileSets;
                this.fileChooserDir = fileChooserDir;
        }
 }
                this.fileChooserDir = fileChooserDir;
        }
 }
index 3d5d3e77283ffbaf136587942370ffeb08f77ba2..f518c2ed87eff47fa7359ebe7b8ed35fe64b41b5 100644 (file)
@@ -24,7 +24,15 @@ public class TileConfigModel implements Serializable {
 
        public void loadImage() throws Util.ImageLoadException {
                imageView = new ImageIcon(
 
        public void loadImage() throws Util.ImageLoadException {
                imageView = new ImageIcon(
-                       Util.extractImage(imageFile).getScaledInstance(-1, TileSelectController.THUMBNAIL_HEIGHT, 0)
+                       Util.extractImage(imageFile).getScaledInstance(-1, TileSetEditController.THUMBNAIL_HEIGHT, 0)
                );
        }
                );
        }
+
+       public TileConfigModel copy() {
+               try {
+                       return new TileConfigModel(imageFile, multiplicity);
+               } catch (Util.ImageLoadException e) {
+                       throw new RuntimeException(e);
+               }
+       }
 }
 }
diff --git a/src/net/jcornell/tile_draw/TileSelectController.java b/src/net/jcornell/tile_draw/TileSelectController.java
deleted file mode 100644 (file)
index c2dd379..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-package net.jcornell.tile_draw;
-
-import java.awt.Dialog;
-import java.awt.Dimension;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.Image;
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.swing.BorderFactory;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSpinner;
-import javax.swing.JTable;
-import javax.swing.SpinnerNumberModel;
-import javax.swing.table.AbstractTableModel;
-
-import net.jcornell.tile_draw.util.Util;
-import net.jcornell.tile_draw.util.JSpinnerTableCellEditor;
-
-
-public class TileSelectController extends AbstractTableModel {
-       public static final int DEFAULT_MULTIPLICITY = 1;
-       public static final int THUMBNAIL_HEIGHT = 50;
-       protected static final Class<?>[] COL_CLASSES = new Class<?>[] {Boolean.class, ImageIcon.class, Integer.class};
-       protected static final String[] COL_NAMES = new String[] {"", "Tile", "Multiplicity"};
-
-       public final List<TileConfigModel> tileModels;
-       public final TileSelectView view;
-       public File fileChooserDir;
-
-       public TileSelectController(List<TileConfigModel> tileModels, File fileChooserDir) {
-               this.tileModels = tileModels;
-               view = new TileSelectView(this);
-               this.fileChooserDir = fileChooserDir;
-               view.setCanBegin(!tileModels.isEmpty());
-       }
-
-       public void addTiles(List<TileConfigModel> toAdd) {
-               int oldSize = tileModels.size();
-               tileModels.addAll(toAdd);
-               super.fireTableRowsInserted(oldSize, tileModels.size());
-               view.setCanBegin(true);
-               saveData();
-       }
-
-       public void dropTiles(List<TileConfigModel> toDrop) {
-               for (TileConfigModel m : toDrop) {
-                       int row = tileModels.indexOf(m);
-                       tileModels.remove(m);
-                       super.fireTableRowsDeleted(row, row);
-               }
-               view.setCanBegin(!tileModels.isEmpty());
-               saveData();
-       }
-
-       public List<TileConfigModel> getSelectedTiles() {
-               return tileModels.stream()
-                       .filter(m -> m.selected)
-                       .collect(Collectors.toList())
-               ;
-       }
-
-       public void beginDraw() {
-               DrawController c = new DrawController(tileModels);
-               c.drawTile();
-               Dialog dialog = c.view.dialog;
-               dialog.pack();
-               dialog.setResizable(false);
-               dialog.setLocationRelativeTo(view.window);
-               dialog.setVisible(true);
-       }
-
-       public void saveData() {
-               SaveData data = new SaveData(tileModels, fileChooserDir);
-               try {
-                       Util.saveData(data);
-               } catch (IOException e) {}
-       }
-
-       @Override
-       public int getRowCount() {
-               return tileModels.size();
-       }
-
-       @Override
-       public int getColumnCount() {
-               return COL_CLASSES.length;
-       }
-
-       @Override
-       public String getColumnName(int col) {
-               return COL_NAMES[col];
-       }
-
-       @Override
-       public Object getValueAt(int row, int col) {
-               TileConfigModel m = tileModels.get(row);
-               return new Object[] {m.selected, m.imageView, m.multiplicity}[col];
-       }
-
-       @Override
-       public void setValueAt(Object value, int row, int col) {
-               TileConfigModel m = tileModels.get(row);
-               if (col == 0) {
-                       m.selected = (boolean) value;
-               } else if (col == 2) {
-                       m.multiplicity = (int) value;
-                       saveData();
-               } else {
-                       throw new AssertionError();
-               }
-       }
-
-       @Override
-       public Class<?> getColumnClass(int col) {
-               return COL_CLASSES[col];
-       }
-
-       @Override
-       public boolean isCellEditable(int row, int col) {
-               return new boolean[] {true, false, true}[col];
-       }
-}
-
-
-class TileSelectView {
-       protected final TileSelectController controller;
-       protected final JTable grid;
-       protected final JButton beginButton;
-       public final JFrame window;
-
-       public TileSelectView(TileSelectController controller) {
-               this.controller = controller;
-               grid = createGrid();
-               beginButton = new JButton("Begin");
-               window = createWindow();
-       }
-
-       protected JTable createGrid() {
-               JTable grid = new JTable(controller);
-               grid.setRowHeight(TileSelectController.THUMBNAIL_HEIGHT);
-               JSpinner spinner = new JSpinner(
-                       new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1)
-               );
-               grid.getColumnModel().getColumn(2).setCellEditor(new JSpinnerTableCellEditor(spinner));
-               return grid;
-       }
-
-       private static TileConfigModel tryTileBuild(File imageFile) {
-               try {
-                       return new TileConfigModel(imageFile, TileSelectController.DEFAULT_MULTIPLICITY);
-               } catch (Util.ImageLoadException e) {
-                       return null;
-               }
-       }
-
-       protected JFrame createWindow() {
-               JFrame frame = new JFrame("Select Tiles");
-               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-               frame.setLayout(new GridBagLayout());
-
-               JScrollPane tilesScrollPane = new JScrollPane(grid);
-               tilesScrollPane.setPreferredSize(new Dimension(400, 150));
-
-               GridBagConstraints tilesPaneC = new GridBagConstraints();
-               tilesPaneC.gridx = 0;
-               tilesPaneC.gridy = GridBagConstraints.RELATIVE;
-               tilesPaneC.fill = GridBagConstraints.BOTH;
-               tilesPaneC.weightx = 1;
-               tilesPaneC.weighty = 1;
-               frame.add(tilesScrollPane, tilesPaneC);
-
-               JButton deleteBtn = new JButton("Delete selected");
-               deleteBtn.addActionListener(e -> {
-                       controller.dropTiles(controller.getSelectedTiles());
-               });
-
-               JButton addBtn = new JButton("Add…");
-               addBtn.addActionListener(e -> {
-                       JFileChooser chooser = new JFileChooser(controller.fileChooserDir);
-                       chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
-                       chooser.setMultiSelectionEnabled(true);
-                       int result = chooser.showOpenDialog(addBtn);
-                       controller.fileChooserDir = chooser.getCurrentDirectory();
-                       controller.saveData();
-                       if (result == JFileChooser.APPROVE_OPTION) {
-                               File[] files = chooser.getSelectedFiles();
-                               List<TileConfigModel> newModels = Arrays.stream(files)
-                                       .map(TileSelectView::tryTileBuild)
-                                       .collect(Collectors.toList())
-                               ;
-                               if (newModels.contains(null)) {
-                                       JOptionPane.showMessageDialog(
-                                               frame, "Failed to load one or more files as images", "Image load error", JOptionPane.WARNING_MESSAGE
-                                       );
-                               }
-                               List<TileConfigModel> okay = newModels.stream()
-                                       .filter(m -> m != null)
-                                       .collect(Collectors.toList())
-                               ;
-                               controller.addTiles(okay);
-                       }
-               });
-
-               beginButton.addActionListener(e -> {
-                       controller.beginDraw();
-               });
-
-               JPanel buttonRow = new JPanel();
-               buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.LINE_AXIS));
-               buttonRow.add(deleteBtn);
-               buttonRow.add(Box.createRigidArea(new Dimension(5, 0)));
-               buttonRow.add(addBtn);
-               buttonRow.add(Box.createHorizontalGlue());
-               buttonRow.add(beginButton);
-               buttonRow.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
-
-               GridBagConstraints btnBarC = new GridBagConstraints();
-               btnBarC.gridx = 0;
-               btnBarC.gridy = GridBagConstraints.RELATIVE;
-               btnBarC.fill = GridBagConstraints.HORIZONTAL;
-               frame.add(buttonRow, btnBarC);
-
-               frame.pack();
-               return frame;
-       }
-
-       public void setCanBegin(boolean canBegin) {
-               beginButton.setEnabled(canBegin);
-       }
-}
diff --git a/src/net/jcornell/tile_draw/TileSetEditController.java b/src/net/jcornell/tile_draw/TileSetEditController.java
new file mode 100644 (file)
index 0000000..6f7656a
--- /dev/null
@@ -0,0 +1,259 @@
+package net.jcornell.tile_draw;
+
+import java.awt.Dialog;
+import java.awt.Dimension;
+import javax.swing.JTextField;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.io.File;
+import java.awt.event.FocusEvent;
+import java.io.IOException;
+import java.util.Arrays;
+import javax.swing.JDialog;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.table.TableCellEditor;
+import javax.swing.ImageIcon;
+import java.awt.Insets;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JComponent;
+import java.awt.Component;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.table.AbstractTableModel;
+
+import net.jcornell.tile_draw.TileSetsController.TileSet;
+import net.jcornell.tile_draw.util.JSpinnerTableCellEditor;
+import net.jcornell.tile_draw.util.Util;
+
+
+public class TileSetEditController extends AbstractTableModel {
+       public static final int DEFAULT_MULTIPLICITY = 1;
+       public static final int THUMBNAIL_HEIGHT = 50;
+       protected static final Class<?>[] COL_CLASSES = new Class<?>[] {Boolean.class, ImageIcon.class, Integer.class};
+       protected static final String[] COL_NAMES = new String[] {"", "Tile", "Multiplicity"};
+
+       public static interface EventListener {
+               public void onSubmitTileSet(TileSet value);
+       }
+
+       public final TileSet tileSet;
+       public TileSetEditSwingView view;
+       public final TileSetsController parent;
+       public final EventListener listener;
+       protected final TileSet originalValue;
+
+       public TileSetEditController(TileSet tileSet, EventListener listener, TileSetsController parent) {
+               this.tileSet = tileSet.copy();
+               this.parent = parent;
+               this.listener = listener;
+               originalValue = tileSet;
+       }
+
+       public void addTiles(List<TileConfigModel> toAdd) {
+               int oldSize = tileSet.tileModels.size();
+               tileSet.tileModels.addAll(toAdd);
+               super.fireTableRowsInserted(oldSize, tileSet.tileModels.size());
+       }
+
+       public void dropTiles(List<TileConfigModel> toDrop) {
+               for (TileConfigModel m : toDrop) {
+                       int row = tileSet.tileModels.indexOf(m);
+                       tileSet.tileModels.remove(m);
+                       super.fireTableRowsDeleted(row, row);
+               }
+       }
+
+       public List<TileConfigModel> getSelectedTiles() {
+               return
+                       tileSet.tileModels.stream()
+                       .filter(m -> m.selected)
+                       .collect(Collectors.toList())
+               ;
+       }
+
+       public File getChooserDir() {
+               return parent.fileChooserDir;
+       }
+
+       public void setChooserDir(File newDir) {
+               parent.setFileChooserDir(newDir);
+       }
+
+       public void submitChanges() {
+               listener.onSubmitTileSet(tileSet);
+       }
+
+       public boolean isClean() {
+               return tileSet.equals(originalValue);
+       }
+
+       @Override
+       public int getRowCount() {
+               return tileSet.tileModels.size();
+       }
+
+       @Override
+       public int getColumnCount() {
+               return COL_CLASSES.length;
+       }
+
+       @Override
+       public String getColumnName(int col) {
+               return COL_NAMES[col];
+       }
+
+       @Override
+       public Object getValueAt(int row, int col) {
+               TileConfigModel m = tileSet.tileModels.get(row);
+               return new Object[] {m.selected, m.imageView, m.multiplicity}[col];
+       }
+
+       @Override
+       public void setValueAt(Object value, int row, int col) {
+               TileConfigModel m = tileSet.tileModels.get(row);
+               if (col == 0) {
+                       m.selected = (boolean) value;
+               } else if (col == 2) {
+                       m.multiplicity = (int) value;
+               } else {
+                       throw new AssertionError();
+               }
+       }
+
+       @Override
+       public Class<?> getColumnClass(int col) {
+               return COL_CLASSES[col];
+       }
+
+       @Override
+       public boolean isCellEditable(int row, int col) {
+               return new boolean[] {true, false, true}[col];
+       }
+}
+
+
+class TileSetEditSwingView {
+       protected final TileSetEditController controller;
+       protected JTextField nameField;
+       protected JTable grid;
+       protected TableCellEditor multColumnEditor;
+       public JOptionPane optionPane;
+       public JDialog dialog;
+
+       public TileSetEditSwingView(TileSetEditController controller, Component dialogParent) {
+               this.controller = controller;
+               populateUi(dialogParent);
+       }
+
+       private static TileConfigModel tryTileBuild(File imageFile) {
+               try {
+                       return new TileConfigModel(imageFile, TileSetEditController.DEFAULT_MULTIPLICITY);
+               } catch (Util.ImageLoadException e) {
+                       return null;
+               }
+       }
+
+       protected void populateUi(Component dialogParent) {
+               grid = new JTable(controller);
+               grid.setRowHeight(TileSetEditController.THUMBNAIL_HEIGHT);
+               JSpinner spinner = new JSpinner(
+                       new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1)
+               );
+               multColumnEditor = new JSpinnerTableCellEditor(spinner);
+               grid.getColumnModel().getColumn(2).setCellEditor(multColumnEditor);
+
+               JPanel panel = new JPanel(new GridBagLayout());
+
+               nameField = new JTextField(controller.originalValue.name);
+               GridBagConstraints nameFieldC = new GridBagConstraints();
+               nameFieldC.gridx = 0;
+               nameFieldC.gridy = GridBagConstraints.RELATIVE;
+               nameFieldC.fill = GridBagConstraints.HORIZONTAL;
+               nameFieldC.insets = new Insets(0, 0, 10, 0);
+               nameFieldC.ipady = 5;
+               panel.add(nameField, nameFieldC);
+
+               JScrollPane tilesScrollPane = new JScrollPane(grid);
+               tilesScrollPane.setPreferredSize(new Dimension(400, 150));
+
+               GridBagConstraints tilesPaneC = new GridBagConstraints();
+               tilesPaneC.gridx = 0;
+               tilesPaneC.gridy = GridBagConstraints.RELATIVE;
+               tilesPaneC.fill = GridBagConstraints.BOTH;
+               tilesPaneC.weightx = 1;
+               tilesPaneC.weighty = 1;
+               panel.add(tilesScrollPane, tilesPaneC);
+
+               JButton deleteBtn = new JButton("Delete selected");
+               deleteBtn.addActionListener(e -> {
+                       controller.dropTiles(controller.getSelectedTiles());
+               });
+
+               JButton addBtn = new JButton("Add…");
+               addBtn.addActionListener(e -> {
+                       JFileChooser chooser = new JFileChooser(controller.getChooserDir());
+                       chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+                       chooser.setMultiSelectionEnabled(true);
+                       int result = chooser.showOpenDialog(addBtn);
+                       controller.setChooserDir(chooser.getCurrentDirectory());
+                       if (result == JFileChooser.APPROVE_OPTION) {
+                               File[] files = chooser.getSelectedFiles();
+                               List<TileConfigModel> newModels = Arrays.stream(files)
+                                       .map(TileSetEditSwingView::tryTileBuild)
+                                       .collect(Collectors.toList())
+                               ;
+                               if (newModels.contains(null)) {
+                                       JOptionPane.showMessageDialog(
+                                               panel, "Failed to load one or more files as images", "Image load error", JOptionPane.WARNING_MESSAGE
+                                       );
+                               }
+                               List<TileConfigModel> okay =
+                                       newModels.stream()
+                                       .filter(m -> m != null)
+                                       .collect(Collectors.toList())
+                               ;
+                               controller.addTiles(okay);
+                       }
+               });
+
+               JPanel buttonRow = new JPanel();
+               buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.LINE_AXIS));
+               buttonRow.add(deleteBtn);
+               buttonRow.add(Box.createRigidArea(new Dimension(5, 0)));
+               buttonRow.add(addBtn);
+               buttonRow.add(Box.createHorizontalGlue());
+
+               GridBagConstraints btnBarC = new GridBagConstraints();
+               btnBarC.gridx = 0;
+               btnBarC.gridy = GridBagConstraints.RELATIVE;
+               btnBarC.fill = GridBagConstraints.HORIZONTAL;
+               btnBarC.insets = new Insets(10, 0, 5, 0);
+               panel.add(buttonRow, btnBarC);
+
+               optionPane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+               dialog = optionPane.createDialog(dialogParent, "Tile set editor");
+               dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+       }
+
+       public void finishEditing() {
+               multColumnEditor.stopCellEditing();
+       }
+
+       public void submitChanges() {
+               controller.tileSet.name = nameField.getText();
+               controller.submitChanges();
+       }
+}
diff --git a/src/net/jcornell/tile_draw/TileSetsController.java b/src/net/jcornell/tile_draw/TileSetsController.java
new file mode 100644 (file)
index 0000000..aa88821
--- /dev/null
@@ -0,0 +1,243 @@
+package net.jcornell.tile_draw;
+
+import javax.swing.JComponent;
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JDialog;
+import javax.swing.JScrollPane;
+import javax.swing.JFrame;
+import java.util.stream.Collectors;
+import javax.swing.ListSelectionModel;
+import javax.swing.JOptionPane;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.Serializable;
+import java.io.File;
+import java.io.IOException;
+import java.awt.BorderLayout;
+import java.awt.Dialog;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import net.jcornell.tile_draw.TileSetsController.TileSet;
+import net.jcornell.tile_draw.util.Util;
+
+
+public class TileSetsController {
+       public static class TileSet implements Serializable {
+               private static final long serialVersionUID = 0;
+
+               public String name;
+               public List<TileConfigModel> tileModels;
+
+               public TileSet(String name, List<TileConfigModel> tileModels) {
+                       this.name = name;
+                       this.tileModels = tileModels;
+               }
+
+               public TileSet copy() {
+                       return new TileSet(name, tileModels.stream().map(TileConfigModel::copy).collect(Collectors.toList()));
+               }
+
+               @Override public boolean equals(Object otherObj) {
+                       TileSet other = (TileSet) otherObj;
+                       return other.name.equals(name) && other.tileModels.equals(tileModels);
+               }
+       }
+
+       protected final List<TileSet> tileSets;
+       protected TileSetsSwingView view;
+       public File fileChooserDir;
+
+       public TileSetsController(List<TileSet> tileSets, File fileChooserDir) {
+               this.tileSets = tileSets;
+               this.fileChooserDir = fileChooserDir;
+       }
+
+       public void addSet(TileSet set) {
+               tileSets.add(set);
+               view.addSet(set);
+               saveData();
+       }
+
+       protected void setSetAt(int index, TileSet value) {
+               tileSets.set(index, value);
+               view.setSetAt(index, value);
+               saveData();
+       }
+
+       public void deleteSetAt(int index) {
+               tileSets.remove(index);
+               view.deleteSetAt(index);
+               saveData();
+       }
+
+       public void saveData() {
+               try {
+                       Util.saveData(new SaveData(tileSets, fileChooserDir));
+               } catch (IOException e) {
+                       view.onSaveFailed(e);
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public void setFileChooserDir(File value) {
+               fileChooserDir = value;
+               saveData();
+       }
+
+       public void beginEdit(int setIndex) {
+               TileSetEditController.EventListener listener = new TileSetEditController.EventListener() {
+                       @Override public void onSubmitTileSet(TileSet value) {
+                               setSetAt(setIndex, value);
+                       }
+               };
+
+               TileSet target = tileSets.get(setIndex);
+               TileSetEditController subController = new TileSetEditController(target, listener, this);
+               TileSetEditSwingView subView = view.getEditView(subController);
+               subController.view = subView;
+               view.doEdit(subView);
+       }
+
+       public void beginDraw(int setIndex) {
+               DrawController c = new DrawController(tileSets.get(setIndex).tileModels);
+               c.drawTile();
+               Dialog dialog = c.view.dialog;
+               dialog.pack();
+               dialog.setResizable(false);
+               dialog.setLocationRelativeTo(view.contentPane);
+               dialog.setVisible(true);
+       }
+}
+
+
+class TileSetsSwingView {
+       protected final TileSetsController controller;
+       protected final DefaultListModel<TileSetWrapper> viewModel;
+       protected final JList<TileSetWrapper> setList;
+       protected final JButton deleteButton, editButton, beginButton;
+
+       public final JComponent contentPane;
+
+       protected static class TileSetWrapper {
+               protected final TileSet tileSet;
+
+               public TileSetWrapper(TileSet tileSet) {
+                       this.tileSet = tileSet;
+               }
+
+               public String toString() {
+                       return tileSet.name;
+               }
+       }
+
+       public TileSetsSwingView(TileSetsController controller) {
+               this.controller = controller;
+               viewModel = new DefaultListModel<>();
+               for (TileSet tileSet : controller.tileSets) {
+                       viewModel.addElement(new TileSetWrapper(tileSet));
+               }
+               setList = new JList<>(viewModel);
+               setList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+               contentPane = new JPanel(new BorderLayout());
+               JPanel buttonBar = new JPanel();
+
+               JButton addButton = new JButton("Add");
+               addButton.addActionListener(e -> {
+                       String newName = (String) JOptionPane.showInputDialog(
+                               contentPane,
+                               "Enter a name for the new tile set",
+                               "New tile set",
+                               JOptionPane.PLAIN_MESSAGE
+                       );
+                       if (newName != null) {
+                               controller.addSet(new TileSet(newName, new ArrayList<>()));
+                       }
+               });
+               buttonBar.add(addButton);
+
+               deleteButton = new JButton("Delete");
+               deleteButton.addActionListener(e -> {
+                       int sel = setList.getSelectedIndex();
+                       if (sel != -1) {
+                               int response = JOptionPane.showConfirmDialog(
+                                       contentPane, "Delete this tile set?", "Confirm delete",
+                                       JOptionPane.OK_CANCEL_OPTION
+                               );
+                               if (response == JOptionPane.OK_OPTION) {
+                                       controller.deleteSetAt(sel);
+                               }
+                       }
+               });
+               buttonBar.add(deleteButton);
+
+               editButton = new JButton("Edit");
+               editButton.addActionListener(e -> {
+                       controller.beginEdit(setList.getSelectedIndex());
+               });
+               buttonBar.add(editButton);
+
+               beginButton = new JButton("Begin");
+               beginButton.addActionListener(e -> {
+                       controller.beginDraw(setList.getSelectedIndex());
+               });
+               buttonBar.add(beginButton);
+
+               setList.addListSelectionListener(e -> updateButtonStates());
+
+               JScrollPane setPane = new JScrollPane(setList);
+
+               contentPane.add(setPane, BorderLayout.CENTER);
+               contentPane.add(buttonBar, BorderLayout.PAGE_END);
+               updateButtonStates();
+       }
+
+       public void addSet(TileSet set) {
+               viewModel.addElement(new TileSetWrapper(set));
+       }
+
+       public void setSetAt(int index, TileSet value) {
+               viewModel.set(index, new TileSetWrapper(value));
+               updateButtonStates();
+       }
+
+       public void deleteSetAt(int index) {
+               viewModel.removeElementAt(index);
+       }
+
+       public TileSetEditSwingView getEditView(TileSetEditController subController) {
+               return new TileSetEditSwingView(subController, contentPane);
+       }
+
+       public void doEdit(TileSetEditSwingView editView) {
+               editView.dialog.setVisible(true);
+               editView.finishEditing();
+               Object value = editView.optionPane.getValue();
+               if (value != null && (int) value == JOptionPane.OK_OPTION) {
+                       editView.submitChanges();
+               }
+       }
+
+       public void updateButtonStates() {
+               int selIndex = setList.getSelectedIndex();
+               boolean hasSelection = selIndex != -1;
+               deleteButton.setEnabled(hasSelection);
+               editButton.setEnabled(hasSelection);
+
+               boolean canBegin = hasSelection && !controller.tileSets.get(selIndex).tileModels.isEmpty();
+               beginButton.setEnabled(canBegin);
+       }
+
+       public void onSaveFailed(Throwable exception) {
+               JOptionPane.showMessageDialog(
+                       contentPane,
+                       "Error saving data: " + exception.toString(),
+                       "Save error",
+                       JOptionPane.ERROR_MESSAGE
+               );
+       }
+}
index 225c45a1e43c0b5ca32eccd67abd578d7aa583b9..a890411e06fe547f4fb0ca216c984b005667ba46 100644 (file)
@@ -21,6 +21,7 @@ import javax.swing.JOptionPane;
 
 import net.jcornell.tile_draw.SaveData;
 import net.jcornell.tile_draw.TileConfigModel;
 
 import net.jcornell.tile_draw.SaveData;
 import net.jcornell.tile_draw.TileConfigModel;
+import net.jcornell.tile_draw.TileSetsController.TileSet;
 
 
 public final class Util {
 
 
 public final class Util {
@@ -93,12 +94,15 @@ public final class Util {
        );
 
        public static SaveData loadData() throws IOException, ClassNotFoundException, Util.ImageLoadException {
        );
 
        public static SaveData loadData() throws IOException, ClassNotFoundException, Util.ImageLoadException {
+               Files.createDirectories(SAVE_LOCATION.getParent());
                SaveData data;
                try (InputStream input = Files.newInputStream(SAVE_LOCATION)) {
                        data = (SaveData) new ObjectInputStream(input).readObject();
                }
                SaveData data;
                try (InputStream input = Files.newInputStream(SAVE_LOCATION)) {
                        data = (SaveData) new ObjectInputStream(input).readObject();
                }
-               for (TileConfigModel m : data.tileModels) {
-                       m.loadImage();
+               for (TileSet set : data.tileSets) {
+                       for (TileConfigModel m : set.tileModels) {
+                               m.loadImage();
+                       }
                }
                return data;
        }
                }
                return data;
        }