package org.leumasjaffe.charsheet.view; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JTabbedPane; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import lombok.AccessLevel; import lombok.NonNull; import lombok.SneakyThrows; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import java.awt.BorderLayout; import javax.swing.JPanel; import javax.swing.JMenuBar; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.KeyStroke; import javax.swing.UIManager; import org.leumasjaffe.charsheet.model.DDCharacter; import org.leumasjaffe.charsheet.model.DDCharacterClass; import org.leumasjaffe.charsheet.model.observable.IntValue; import org.leumasjaffe.charsheet.observer.ObserverHelper; import org.leumasjaffe.observer.ObserverDispatch; import java.awt.event.KeyEvent; import java.awt.event.WindowEvent; import java.io.File; import java.util.ArrayList; import java.util.List; import java.awt.event.InputEvent; @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) public class D20Sheet extends JFrame { /** * */ private static final long serialVersionUID = 1L; ObjectMapper mapper = new ObjectMapper(); @NonFinal File currentlyOpenFile = null; @NonFinal @NonNull DDCharacter model = new DDCharacter(); JTabbedPane tabbedPane; SummaryTab summaryTab; List classTabs = new ArrayList<>(); JPanel abilitiesTab; SkillTab skillTab; EquipmentTab equipmentTab; private DeveloperMenu developerMenu; public D20Sheet() { UIManager.put("CheckBox.disabledText", UIManager.get("CheckBox.foreground")); // Set up local state variables final JFileChooser fc = new JFileChooser("resources"); mapper.registerModule(new Jdk8Module()); // Set up GUI Data (WindowBuilder) tabbedPane = new JTabbedPane(JTabbedPane.TOP); getContentPane().add(tabbedPane, BorderLayout.CENTER); summaryTab = new SummaryTab(); abilitiesTab = new JPanel(); skillTab = new SkillTab(); equipmentTab = new EquipmentTab(); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu mnFile = new JMenu("File"); menuBar.add(mnFile); JMenuItem mntmNew = new JMenuItem("New"); mntmNew.addActionListener(e -> { setModel(new DDCharacter()); }); mntmNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK)); mnFile.add(mntmNew); JMenuItem mntmOpen = new JMenuItem("Open"); mntmOpen.addActionListener(e -> { int rv = fc.showOpenDialog(this); if ( rv == JFileChooser.APPROVE_OPTION ) { loadModelResource( fc.getSelectedFile() ); tabbedPane.setSelectedIndex(0); } }); mntmOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK)); mnFile.add(mntmOpen); JMenuItem mntmSave = new JMenuItem("Save"); mntmSave.addActionListener( e -> { if ( currentlyOpenFile == null ) { saveAs(fc); } else { saveModelResource(); } } ); mntmSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK)); mnFile.add(mntmSave); JMenuItem mntmSaveAs = new JMenuItem("Save As"); mntmSaveAs.addActionListener( e -> saveAs(fc) ); mntmSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); mnFile.add(mntmSaveAs); JMenuItem mntmConfig = new JMenuItem("Config"); mntmConfig.addActionListener( e -> DialogBuilder.showConfigDialog(this) ); mntmConfig.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.ALT_DOWN_MASK)); mnFile.add(mntmConfig); JMenuItem mntmExit = new JMenuItem("Exit"); mntmExit.addActionListener( e -> { this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } ); mntmExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_MASK)); mnFile.add(mntmExit); JMenu mnSession = new JMenu("Session"); menuBar.add(mnSession); JMenuItem mntmGainExperience = new JMenuItem("Gain Experience"); mntmGainExperience.addActionListener(e -> { // TODO move to own thing? final String input = JOptionPane.showInputDialog("How much experience"); if (input == null || input.isEmpty()) return; final int exp = Integer.parseInt(input); if (exp <= 0) return; final IntValue mdlExp = this.model.getExperience(); mdlExp.value(mdlExp.value()+exp); ObserverDispatch.notifySubscribers(mdlExp, this); DialogBuilder.showLevelUpDialog(this, this.model, 0); }); mnSession.add(mntmGainExperience); JMenuItem mntmTakeRest = new JMenuItem("Take Rest"); mntmTakeRest.addActionListener(e -> { // Step 0: Gather information about the rest // Step 0.1: Check if 8-hour or full day // Step 0.2: Check if Long-Term-Care // Step 1: Heal Character according to info // Step 1.1: Heal non-lethal damage // Step 1.2: Heal ability damage // Step 2: Regenerate spells prepared // Step 2.1: If Spontaneous, skip (2) for (DDCharacterClass dclass : model.getClasses()) { dclass.getSpellBook().ifPresent(sb -> { if (sb.preparesSpells()) { DialogBuilder.createPrepareSpellsDialog(this, model, dclass); } else { for (int i = 0; i < dclass.getHighestSpellLevel(); ++i) { sb.prepareSpells(i, null); } } }); } // Step N: regenerate spellbooks for (DDCharacterClass dclass : model.getClasses()) { ObserverHelper.notifyObservableHierarchy(dclass, this); } }); mnSession.add(mntmTakeRest); developerMenu = new DeveloperMenu(this); menuBar.add(developerMenu); // Set up post-GUI dependencies setModel(model); } public D20Sheet(final String initialFile) { this(); loadModelResource(new File(initialFile)); } private void reorderTabs() { this.tabbedPane.removeAll(); tabbedPane.addTab("Summary", null, summaryTab, null); classTabs.forEach( t -> tabbedPane.addTab(t.getName(), null, t, null) ); tabbedPane.addTab("Abilities", null, abilitiesTab, "Racial and Class Abilities, Feats, etc."); tabbedPane.addTab("Skills", null, skillTab, null); tabbedPane.addTab("Gear & Items", null, equipmentTab, null); } private void saveAs(final JFileChooser fc) { int rv = fc.showSaveDialog(this); if ( rv == JFileChooser.APPROVE_OPTION ) { currentlyOpenFile = fc.getSelectedFile(); saveModelResource(); } } @SneakyThrows private void loadModelResource(File selectedFile) { currentlyOpenFile = selectedFile; setModel(mapper.readValue(selectedFile, DDCharacter.class)); } @SneakyThrows private void saveModelResource() { if ( currentlyOpenFile != null ) { mapper.writeValue(currentlyOpenFile, model); } } private void setModel(DDCharacter model) { classTabs.clear(); this.model = model; summaryTab.setModel(model); model.getClasses().stream().forEach(cc -> classTabs.add(new ClassTab(model, cc))); equipmentTab.setModel(model); skillTab.setModel(model); developerMenu.setModel(model); reorderTabs(); } }