SelectSpellsPanel.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package org.leumasjaffe.charsheet.view.magic;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.Collection;
  5. import java.util.List;
  6. import javax.swing.JPanel;
  7. import javax.swing.JPopupMenu;
  8. import javax.swing.JTable;
  9. import javax.swing.ListSelectionModel;
  10. import org.leumasjaffe.charsheet.model.DDCharacter;
  11. import org.leumasjaffe.charsheet.model.DDCharacterClass;
  12. import org.leumasjaffe.charsheet.model.magic.DDSpell;
  13. import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
  14. import org.leumasjaffe.charsheet.model.observable.BoolGate;
  15. import org.leumasjaffe.charsheet.model.observable.IntValue;
  16. import org.leumasjaffe.charsheet.util.AbilityHelper;
  17. import org.leumasjaffe.event.SelectTableRowPopupMenuListener;
  18. import org.leumasjaffe.format.StringHelper;
  19. import lombok.AccessLevel;
  20. import lombok.AllArgsConstructor;
  21. import lombok.Getter;
  22. import lombok.experimental.FieldDefaults;
  23. import java.awt.GridBagLayout;
  24. import java.awt.Dimension;
  25. import java.awt.GridBagConstraints;
  26. import java.awt.Insets;
  27. import javax.swing.border.BevelBorder;
  28. import javax.swing.border.SoftBevelBorder;
  29. import javax.swing.table.AbstractTableModel;
  30. import javax.swing.JButton;
  31. import javax.swing.JFrame;
  32. import javax.swing.JMenuItem;
  33. import javax.swing.JOptionPane;
  34. import javax.swing.JScrollPane;
  35. @SuppressWarnings("serial")
  36. @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
  37. public class SelectSpellsPanel extends JPanel {
  38. private static final String NONE = "<none>";
  39. @AllArgsConstructor
  40. private static class SelectSpellModel extends AbstractTableModel {
  41. /**
  42. *
  43. */
  44. private static final long serialVersionUID = 1L;
  45. final Object[] data;
  46. @Override
  47. public int getRowCount() {
  48. return data == null ? 0 : data.length;
  49. }
  50. @Override
  51. public int getColumnCount() {
  52. return 1;
  53. }
  54. @Override
  55. public Object getValueAt(int rowIndex, int columnIndex) {
  56. if (columnIndex != 0) { throw new IllegalArgumentException("There is only 1 column"); }
  57. return data[rowIndex];
  58. }
  59. @Override
  60. public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
  61. if (columnIndex != 0) { throw new IllegalArgumentException("There is only 1 column"); }
  62. data[rowIndex] = aValue;
  63. }
  64. }
  65. @AllArgsConstructor
  66. @FieldDefaults(level=AccessLevel.PUBLIC, makeFinal=true)
  67. public static class Info {
  68. DDCharacter chara;
  69. DDCharacterClass dclass;
  70. }
  71. public static final String READY = "Is Filled Out";
  72. @Getter Collection<DDSpell> prepared;
  73. boolean allowsDuplicates;
  74. IntValue sharedValue;
  75. SelectSpellModel modelPrepared, modelKnown;
  76. public SelectSpellsPanel(Info info, BoolGate.Handle gate, int level, Collection<DDSpell> prepared, int toPrepare,
  77. Collection<DDSpell> avail, boolean allowsDuplicates, IntValue sharedValue) {
  78. this.allowsDuplicates = allowsDuplicates;
  79. this.sharedValue = sharedValue;
  80. final DDSpellbook spellBook = info.dclass.getSpellBook().get();
  81. this.prepared = new ArrayList<>(prepared);
  82. final List<DDSpell> known = new ArrayList<>(avail);
  83. this.modelPrepared = new SelectSpellModel(createPrepareModel(prepared, toPrepare));
  84. this.modelKnown = new SelectSpellModel(known.stream().map(DDSpell::getName).toArray());
  85. putClientProperty(READY, countNone() == 0);
  86. sharedValue.value(sharedValue.value() - this.modelPrepared.data.length + countNone());
  87. GridBagLayout gridBagLayout = new GridBagLayout();
  88. gridBagLayout.columnWidths = new int[]{0, 40, 0, 0};
  89. gridBagLayout.rowHeights = new int[]{20, 0, 0};
  90. gridBagLayout.columnWeights = new double[]{1.0, 0.0, 1.0, Double.MIN_VALUE};
  91. gridBagLayout.rowWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
  92. setLayout(gridBagLayout);
  93. JPanel panel = new ChooseSpellsPerDayHeader(level, spellBook, AbilityHelper.get(info.chara, info.dclass));
  94. GridBagConstraints gbc_panel = new GridBagConstraints();
  95. gbc_panel.gridwidth = 3;
  96. gbc_panel.insets = new Insets(0, 0, 5, 5);
  97. gbc_panel.fill = GridBagConstraints.BOTH;
  98. gbc_panel.gridx = 0;
  99. gbc_panel.gridy = 0;
  100. add(panel, gbc_panel);
  101. JScrollPane scrollPane_1 = new JScrollPane();
  102. scrollPane_1.setPreferredSize(new Dimension(175, 200));
  103. scrollPane_1.setViewportBorder(new SoftBevelBorder(BevelBorder.LOWERED, null, null, null, null));
  104. GridBagConstraints gbc_scrollPane_1 = new GridBagConstraints();
  105. gbc_scrollPane_1.insets = new Insets(0, 0, 0, 5);
  106. gbc_scrollPane_1.fill = GridBagConstraints.BOTH;
  107. gbc_scrollPane_1.gridx = 0;
  108. gbc_scrollPane_1.gridy = 1;
  109. add(scrollPane_1, gbc_scrollPane_1);
  110. JTable tablePrepared = new JTable(modelPrepared);
  111. tablePrepared.setTableHeader(null);
  112. scrollPane_1.setViewportView(tablePrepared);
  113. tablePrepared.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  114. JPanel panelDivider = new JPanel();
  115. GridBagConstraints gbc_panelDivider = new GridBagConstraints();
  116. gbc_panelDivider.insets = new Insets(0, 0, 0, 5);
  117. gbc_panelDivider.fill = GridBagConstraints.BOTH;
  118. gbc_panelDivider.gridx = 1;
  119. gbc_panelDivider.gridy = 1;
  120. add(panelDivider, gbc_panelDivider);
  121. GridBagLayout gbl_panelDivider = new GridBagLayout();
  122. gbl_panelDivider.columnWidths = new int[]{0, 0};
  123. gbl_panelDivider.rowHeights = new int[]{0, 0, 0, 0, 0};
  124. gbl_panelDivider.columnWeights = new double[]{0.0, Double.MIN_VALUE};
  125. gbl_panelDivider.rowWeights = new double[]{1.0, 0.0, 0.0, 1.0, Double.MIN_VALUE};
  126. panelDivider.setLayout(gbl_panelDivider);
  127. JScrollPane scrollPane = new JScrollPane();
  128. scrollPane.setPreferredSize(new Dimension(175, 200));
  129. scrollPane.setViewportBorder(new SoftBevelBorder(BevelBorder.LOWERED, null, null, null, null));
  130. GridBagConstraints gbc_scrollPane = new GridBagConstraints();
  131. gbc_scrollPane.insets = new Insets(0, 0, 0, 5);
  132. gbc_scrollPane.fill = GridBagConstraints.BOTH;
  133. gbc_scrollPane.gridx = 2;
  134. gbc_scrollPane.gridy = 1;
  135. add(scrollPane, gbc_scrollPane);
  136. JTable tableKnown = new JTable(modelKnown);
  137. tableKnown.setTableHeader(null);
  138. scrollPane.setViewportView(tableKnown);
  139. tableKnown.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  140. JPopupMenu popup = new JPopupMenu();
  141. popup.addPopupMenuListener(new SelectTableRowPopupMenuListener(popup, tableKnown));
  142. JMenuItem mntmInfo = new JMenuItem("Info");
  143. mntmInfo.addActionListener( e -> {
  144. DDSpell spell = known.get(tableKnown.getSelectedRow());
  145. JFrame frame = new JFrame(spell.getName() + " (" + info.dclass.getName() + " " + level + ")");
  146. frame.add(new SpellInfoPanel(info.chara, info.dclass, spell));
  147. frame.pack();
  148. frame.setVisible(true);
  149. });
  150. popup.add(mntmInfo);
  151. tableKnown.setComponentPopupMenu(popup);
  152. JButton button = new JButton(">>");
  153. button.setMargin(new Insets(2, 8, 2, 8));
  154. GridBagConstraints gbc_button = new GridBagConstraints();
  155. gbc_button.insets = new Insets(0, 0, 5, 0);
  156. gbc_button.gridx = 0;
  157. gbc_button.gridy = 1;
  158. panelDivider.add(button, gbc_button);
  159. button.addActionListener(e -> {
  160. final int row = tablePrepared.getSelectedRow();
  161. if (row != -1 && !modelPrepared.data[row].equals(NONE)) {
  162. sharedValue.value(sharedValue.value() + 1);
  163. modelPrepared.setValueAt(NONE, row, 0);
  164. }
  165. tablePrepared.getSelectionModel().clearSelection();
  166. tablePrepared.repaint();
  167. if ((Boolean) getClientProperty(READY)) {
  168. putClientProperty(READY, false);
  169. }
  170. });
  171. JButton button_1 = new JButton("<<");
  172. button_1.setMargin(new Insets(2, 8, 2, 8));
  173. GridBagConstraints gbc_button_1 = new GridBagConstraints();
  174. gbc_button_1.insets = new Insets(0, 0, 5, 0);
  175. gbc_button_1.gridx = 0;
  176. gbc_button_1.gridy = 2;
  177. panelDivider.add(button_1, gbc_button_1);
  178. button_1.addActionListener(e -> {
  179. final int[] rows = tableKnown.getSelectedRows();
  180. final int[] orows = tablePrepared.getSelectedRows();
  181. if (sharedValue.value() == 0) {
  182. JOptionPane.showMessageDialog(this, "You have exceeded the shared limit on new spells",
  183. "Error", JOptionPane.ERROR_MESSAGE);
  184. } else if (orows.length >= rows.length) {
  185. for (int i = 0; i < rows.length; ++i) {
  186. if (wouldHaveIllegalDuplicate(rows[i])) continue;
  187. modelPrepared.data[orows[i]] = modelKnown.data[rows[i]];
  188. sharedValue.value(sharedValue.value() - 1);
  189. }
  190. } else if (orows.length == 0 && countNone() >= rows.length) {
  191. replace(rows);
  192. } else {
  193. final String message = StringHelper.format(
  194. "Unable to assign new spells, more spells were selected ({}) than were avaliable ({})",
  195. rows.length, orows.length == 0 ? countNone() : orows.length);
  196. JOptionPane.showMessageDialog(this, message, "Error", JOptionPane.ERROR_MESSAGE);
  197. }
  198. tableKnown.getSelectionModel().clearSelection();
  199. tablePrepared.getSelectionModel().clearSelection();
  200. tablePrepared.repaint();
  201. if (!gate.get() && !Arrays.asList(modelPrepared.data).contains(NONE)) {
  202. this.prepared.clear();
  203. for (Object o : modelPrepared.data) {
  204. this.prepared.add(DDSpell.fromString((String) o)); // TODO
  205. }
  206. gate.set(true);
  207. }
  208. });
  209. }
  210. public SelectSpellsPanel(Info info, BoolGate.Handle gate, int level,
  211. Collection<DDSpell> prepared, Collection<DDSpell> avail) {
  212. this(info, gate, level, prepared, 0, avail, true, new IntValue(-1));
  213. }
  214. private boolean wouldHaveIllegalDuplicate(int row) {
  215. return !this.allowsDuplicates && Arrays.asList(modelPrepared.data).contains(modelKnown.data[row]);
  216. }
  217. private String[] createPrepareModel(Collection<DDSpell> prepared, int toPrepare) {
  218. if (toPrepare <= prepared.size()) {
  219. return prepared.stream().map(DDSpell::getName).toArray(String[]::new);
  220. } else {
  221. String[] data = new String[toPrepare];
  222. int i = 0;
  223. for (DDSpell sp : prepared) {
  224. data[i++] = sp.getName();
  225. }
  226. for (; i < toPrepare; ++i) {
  227. data[i] = NONE;
  228. }
  229. return data;
  230. }
  231. }
  232. private void replace(int[] rows) {
  233. for (int i = 0; i < rows.length; ++i) {
  234. if (wouldHaveIllegalDuplicate(i)) continue;
  235. for (int j = 0; j < modelPrepared.data.length; ++j) {
  236. if (!modelPrepared.data[j].equals(NONE)) continue;
  237. modelPrepared.data[j] = modelKnown.data[rows[i]];
  238. sharedValue.value(sharedValue.value() - 1);
  239. break;
  240. }
  241. }
  242. }
  243. private int countNone() {
  244. int cnt = 0;
  245. for (Object o : modelPrepared.data) {
  246. if (o.equals(NONE)) ++cnt;
  247. }
  248. return cnt;
  249. }
  250. }