package org.leumasjaffe.charsheet.view.magic; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.ListSelectionModel; import org.leumasjaffe.charsheet.model.DDCharacter; import org.leumasjaffe.charsheet.model.DDCharacterClass; import org.leumasjaffe.charsheet.model.magic.DDSpell; import org.leumasjaffe.charsheet.model.magic.DDSpellbook; import org.leumasjaffe.charsheet.model.observable.BoolGate; import org.leumasjaffe.charsheet.model.observable.IntValue; import org.leumasjaffe.charsheet.util.AbilityHelper; import org.leumasjaffe.event.SelectTableRowPopupMenuListener; import org.leumasjaffe.format.StringHelper; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.FieldDefaults; import java.awt.GridBagLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.Insets; import javax.swing.border.BevelBorder; import javax.swing.border.SoftBevelBorder; import javax.swing.table.AbstractTableModel; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JScrollPane; @SuppressWarnings("serial") @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) public class SelectSpellsPanel extends JPanel { private static final String NONE = ""; @AllArgsConstructor private static class SelectSpellModel extends AbstractTableModel { /** * */ private static final long serialVersionUID = 1L; final Object[] data; @Override public int getRowCount() { return data == null ? 0 : data.length; } @Override public int getColumnCount() { return 1; } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (columnIndex != 0) { throw new IllegalArgumentException("There is only 1 column"); } return data[rowIndex]; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex != 0) { throw new IllegalArgumentException("There is only 1 column"); } data[rowIndex] = aValue; } } @AllArgsConstructor @FieldDefaults(level=AccessLevel.PUBLIC, makeFinal=true) public static class Info { DDCharacter chara; DDCharacterClass dclass; } public static final String READY = "Is Filled Out"; @Getter Collection prepared; boolean allowsDuplicates; IntValue sharedValue; SelectSpellModel modelPrepared, modelKnown; public SelectSpellsPanel(Info info, BoolGate.Handle gate, int level, Collection prepared, int toPrepare, Collection avail, boolean allowsDuplicates, IntValue sharedValue) { this.allowsDuplicates = allowsDuplicates; this.sharedValue = sharedValue; final DDSpellbook spellBook = info.dclass.getSpellBook().get(); this.prepared = new ArrayList<>(prepared); final List known = new ArrayList<>(avail); this.modelPrepared = new SelectSpellModel(createPrepareModel(prepared, toPrepare)); this.modelKnown = new SelectSpellModel(known.stream().map(DDSpell::getName).toArray()); putClientProperty(READY, countNone() == 0); sharedValue.value(sharedValue.value() - this.modelPrepared.data.length + countNone()); GridBagLayout gridBagLayout = new GridBagLayout(); gridBagLayout.columnWidths = new int[]{0, 40, 0, 0}; gridBagLayout.rowHeights = new int[]{20, 0, 0}; gridBagLayout.columnWeights = new double[]{1.0, 0.0, 1.0, Double.MIN_VALUE}; gridBagLayout.rowWeights = new double[]{0.0, 1.0, Double.MIN_VALUE}; setLayout(gridBagLayout); JPanel panel = new ChooseSpellsPerDayHeader(level, spellBook, AbilityHelper.get(info.chara, info.dclass)); GridBagConstraints gbc_panel = new GridBagConstraints(); gbc_panel.gridwidth = 3; gbc_panel.insets = new Insets(0, 0, 5, 5); gbc_panel.fill = GridBagConstraints.BOTH; gbc_panel.gridx = 0; gbc_panel.gridy = 0; add(panel, gbc_panel); JScrollPane scrollPane_1 = new JScrollPane(); scrollPane_1.setPreferredSize(new Dimension(175, 200)); scrollPane_1.setViewportBorder(new SoftBevelBorder(BevelBorder.LOWERED, null, null, null, null)); GridBagConstraints gbc_scrollPane_1 = new GridBagConstraints(); gbc_scrollPane_1.insets = new Insets(0, 0, 0, 5); gbc_scrollPane_1.fill = GridBagConstraints.BOTH; gbc_scrollPane_1.gridx = 0; gbc_scrollPane_1.gridy = 1; add(scrollPane_1, gbc_scrollPane_1); JTable tablePrepared = new JTable(modelPrepared); tablePrepared.setTableHeader(null); scrollPane_1.setViewportView(tablePrepared); tablePrepared.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JPanel panelDivider = new JPanel(); GridBagConstraints gbc_panelDivider = new GridBagConstraints(); gbc_panelDivider.insets = new Insets(0, 0, 0, 5); gbc_panelDivider.fill = GridBagConstraints.BOTH; gbc_panelDivider.gridx = 1; gbc_panelDivider.gridy = 1; add(panelDivider, gbc_panelDivider); GridBagLayout gbl_panelDivider = new GridBagLayout(); gbl_panelDivider.columnWidths = new int[]{0, 0}; gbl_panelDivider.rowHeights = new int[]{0, 0, 0, 0, 0}; gbl_panelDivider.columnWeights = new double[]{0.0, Double.MIN_VALUE}; gbl_panelDivider.rowWeights = new double[]{1.0, 0.0, 0.0, 1.0, Double.MIN_VALUE}; panelDivider.setLayout(gbl_panelDivider); JScrollPane scrollPane = new JScrollPane(); scrollPane.setPreferredSize(new Dimension(175, 200)); scrollPane.setViewportBorder(new SoftBevelBorder(BevelBorder.LOWERED, null, null, null, null)); GridBagConstraints gbc_scrollPane = new GridBagConstraints(); gbc_scrollPane.insets = new Insets(0, 0, 0, 5); gbc_scrollPane.fill = GridBagConstraints.BOTH; gbc_scrollPane.gridx = 2; gbc_scrollPane.gridy = 1; add(scrollPane, gbc_scrollPane); JTable tableKnown = new JTable(modelKnown); tableKnown.setTableHeader(null); scrollPane.setViewportView(tableKnown); tableKnown.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JPopupMenu popup = new JPopupMenu(); popup.addPopupMenuListener(new SelectTableRowPopupMenuListener(popup, tableKnown)); JMenuItem mntmInfo = new JMenuItem("Info"); mntmInfo.addActionListener( e -> { DDSpell spell = known.get(tableKnown.getSelectedRow()); JFrame frame = new JFrame(spell.getName() + " (" + info.dclass.getName() + " " + level + ")"); frame.add(new SpellInfoPanel(info.chara, info.dclass, spell)); frame.pack(); frame.setVisible(true); }); popup.add(mntmInfo); tableKnown.setComponentPopupMenu(popup); JButton button = new JButton(">>"); button.setMargin(new Insets(2, 8, 2, 8)); GridBagConstraints gbc_button = new GridBagConstraints(); gbc_button.insets = new Insets(0, 0, 5, 0); gbc_button.gridx = 0; gbc_button.gridy = 1; panelDivider.add(button, gbc_button); button.addActionListener(e -> { final int row = tablePrepared.getSelectedRow(); if (row != -1 && !modelPrepared.data[row].equals(NONE)) { sharedValue.value(sharedValue.value() + 1); modelPrepared.setValueAt(NONE, row, 0); } tablePrepared.getSelectionModel().clearSelection(); tablePrepared.repaint(); if ((Boolean) getClientProperty(READY)) { putClientProperty(READY, false); } }); JButton button_1 = new JButton("<<"); button_1.setMargin(new Insets(2, 8, 2, 8)); GridBagConstraints gbc_button_1 = new GridBagConstraints(); gbc_button_1.insets = new Insets(0, 0, 5, 0); gbc_button_1.gridx = 0; gbc_button_1.gridy = 2; panelDivider.add(button_1, gbc_button_1); button_1.addActionListener(e -> { final int[] rows = tableKnown.getSelectedRows(); final int[] orows = tablePrepared.getSelectedRows(); if (sharedValue.value() == 0) { JOptionPane.showMessageDialog(this, "You have exceeded the shared limit on new spells", "Error", JOptionPane.ERROR_MESSAGE); } else if (orows.length >= rows.length) { for (int i = 0; i < rows.length; ++i) { if (wouldHaveIllegalDuplicate(rows[i])) continue; modelPrepared.data[orows[i]] = modelKnown.data[rows[i]]; sharedValue.value(sharedValue.value() - 1); } } else if (orows.length == 0 && countNone() >= rows.length) { replace(rows); } else { final String message = StringHelper.format( "Unable to assign new spells, more spells were selected ({}) than were avaliable ({})", rows.length, orows.length == 0 ? countNone() : orows.length); JOptionPane.showMessageDialog(this, message, "Error", JOptionPane.ERROR_MESSAGE); } tableKnown.getSelectionModel().clearSelection(); tablePrepared.getSelectionModel().clearSelection(); tablePrepared.repaint(); if (!gate.get() && !Arrays.asList(modelPrepared.data).contains(NONE)) { this.prepared.clear(); for (Object o : modelPrepared.data) { this.prepared.add(DDSpell.fromString((String) o)); // TODO } gate.set(true); } }); } public SelectSpellsPanel(Info info, BoolGate.Handle gate, int level, Collection prepared, Collection avail) { this(info, gate, level, prepared, 0, avail, true, new IntValue(-1)); } private boolean wouldHaveIllegalDuplicate(int row) { return !this.allowsDuplicates && Arrays.asList(modelPrepared.data).contains(modelKnown.data[row]); } private String[] createPrepareModel(Collection prepared, int toPrepare) { if (toPrepare <= prepared.size()) { return prepared.stream().map(DDSpell::getName).toArray(String[]::new); } else { String[] data = new String[toPrepare]; int i = 0; for (DDSpell sp : prepared) { data[i++] = sp.getName(); } for (; i < toPrepare; ++i) { data[i] = NONE; } return data; } } private void replace(int[] rows) { for (int i = 0; i < rows.length; ++i) { if (wouldHaveIllegalDuplicate(i)) continue; for (int j = 0; j < modelPrepared.data.length; ++j) { if (!modelPrepared.data[j].equals(NONE)) continue; modelPrepared.data[j] = modelKnown.data[rows[i]]; sharedValue.value(sharedValue.value() - 1); break; } } } private int countNone() { int cnt = 0; for (Object o : modelPrepared.data) { if (o.equals(NONE)) ++cnt; } return cnt; } }