Procházet zdrojové kódy

Merge branch 'skills_display' into feats

* skills_display:
  Transform SkillTab to use listener behavior. Send appropriate signals from SkillLevelUpPanel
  Fix bugs in code ordering/missing unsubscribe
  Remove wildCard skills that were added in choosing, but no points were allocated towards - to keep down clutter. Fix rendering on add.
  Allow adding instantiations of wildcard skills on button press. To do this, the following changes were made to SkillLevelUpPanel: - Change to use an Ordered Map of (Skill Name) -> (GUI Element). This allows order preservation while taking advantage of the usefulness of features like computeIfAbsent. - Capture more variables as member variables to reduce lambda nesting. - Add a listener that computes/regenerates the list of components.
  Make longer-named skills use tooltip display.
  Add a button for creating a new skill from a wildcard skill. Adjust GUI elements for SkilLevelUpLine components. Use the isWildcardSkill() function instead of doing a manual check.
  Add an isWildcard property to DDSkill. Get rid of stupid Optional in DDSkillPrototype Create a skill if one that does not exist is requested.
  Add WildcardSkillLevelUpLine, which allows the handling of special skills like 'Profession'. This only adds the GUI elements, and not the manipulations required.
  Extract out interface
  Rename SkillLevelUpLine in preparation of adding a special form for '(*)'-type skills.
  Put 'all' skills into DDSkills object. TODO: Handle '(*)' skills
Sam Jaffe před 8 roky
rodič
revize
c95154b4c8

+ 4 - 0
src/main/lombok/org/leumasjaffe/charsheet/model/skill/DDSkill.java

@@ -23,6 +23,10 @@ public class DDSkill extends Observable.Instance {
 	// Unless you gain it as a class skill later, in which case it might be in-between
 	@Setter(value=AccessLevel.PRIVATE) int pointsSpent = 0;
 	
+	public DDSkill(String name) {
+		this(new DDSkillPrototype(name));
+	}
+	
 	public DDSkill(DDSkillPrototype proto) {
 		this.name = proto;
 	}

+ 21 - 12
src/main/lombok/org/leumasjaffe/charsheet/model/skill/DDSkillPrototype.java

@@ -6,7 +6,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -18,13 +17,13 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.experimental.FieldDefaults;
 
-@AllArgsConstructor
-@Data
+@AllArgsConstructor @Data
 @FieldDefaults(level=AccessLevel.PRIVATE)
-public class DDSkillPrototype {
+class DDSkillPrototype {
 	final String name;
 	final boolean requiresTraining;
-	String ability;
+	String ability = "";
+	boolean fromWildcardSkill = false;
 	
 	private static final Map<String, DDSkillPrototype> prototypes;
 	
@@ -43,23 +42,33 @@ public class DDSkillPrototype {
 	}
 	
 	public DDSkillPrototype(String name) {
-		DDSkillPrototype base = getPrototype(name).get();
+		DDSkillPrototype base = getPrototype(name);
 		this.name = name;
 		this.requiresTraining = base.requiresTraining;
 		this.ability = base.ability;
+		this.fromWildcardSkill = base.fromWildcardSkill;
 	}
 	
-	public static Optional<DDSkillPrototype> getPrototype(String name) {
+	public boolean isWildcardSkill() {
+		return name.contains("(*)");
+	}
+	
+	public static DDSkillPrototype getPrototype(String name) {
 		if (name.contains("(")) {
 			DDSkillPrototype proto = prototypes.get(name.replaceFirst("\\(.*\\)", "(*)"));
-			if ( proto == null ) return Optional.empty();
-			return Optional.of(new DDSkillPrototype(name, proto.requiresTraining, proto.ability));
-		} else {
-			return Optional.ofNullable(prototypes.get(name));
+			if (proto != null) { return new DDSkillPrototype(name, proto.requiresTraining, proto.ability, true); }
+		} 
+		if (prototypes.containsKey(name)) {
+			return prototypes.get(name);
 		}
+		throw new IllegalStateException("Unknown skill '" + name + "'");
+	}
+	
+	public static Stream<DDSkillPrototype> all() {
+		return prototypes.values().stream();
 	}
 
 	public static Stream<DDSkillPrototype> untrained() {
 		return prototypes.values().stream().filter(p -> !p.requiresTraining && !p.getName().contains("(*)"));
-	}
+	}	
 }

+ 12 - 4
src/main/lombok/org/leumasjaffe/charsheet/model/skill/DDSkills.java

@@ -3,7 +3,6 @@ package org.leumasjaffe.charsheet.model.skill;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
-import java.util.Optional;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
@@ -21,7 +20,7 @@ public class DDSkills extends Observable.Instance {
 	
 	@JsonCreator
 	public DDSkills(Collection<DDSkill> in) {
-		skills.putAll(DDSkillPrototype.untrained().collect(Collectors.toMap(t -> t.getName(), t -> new DDSkill(t))));
+		skills.putAll(DDSkillPrototype.all().collect(Collectors.toMap(t -> t.getName(), t -> new DDSkill(t))));
 		skills.putAll(in.stream().collect(Collectors.toMap(t -> t.getName(), t -> t)));
 	}
 	
@@ -34,7 +33,16 @@ public class DDSkills extends Observable.Instance {
 		return Collections.unmodifiableCollection(skills.values());
 	}
 
-	public Optional<DDSkill> getSkill(String name) {
-		return Optional.ofNullable(skills.get(name));
+	public DDSkill getSkill(String name) {
+		skills.computeIfAbsent(name, DDSkill::new);
+		return skills.get(name);
+	}
+	
+	public void removeSkill(DDSkill skill) {
+		final DDSkill removed = skills.get(skill.getName());
+		if (removed != null && !skill.equals(removed)) {
+			throw new IllegalArgumentException("Attempting to remove a skill (" + skill.getName() + ") not in the object");
+		}
+		skills.remove(skill.getName());
 	}
 }

+ 51 - 24
src/main/lombok/org/leumasjaffe/charsheet/view/SkillTab.java

@@ -6,28 +6,33 @@ import javax.swing.JScrollPane;
 import java.awt.GridBagConstraints;
 import org.jdesktop.swingx.VerticalLayout;
 import org.leumasjaffe.charsheet.model.DDCharacter;
+import org.leumasjaffe.charsheet.model.skill.DDSkill;
 import org.leumasjaffe.charsheet.model.skill.DDSkills;
 import org.leumasjaffe.charsheet.view.skills.SkillLine;
+import org.leumasjaffe.observer.ObservableListener;
+import org.leumasjaffe.observer.ObserverDispatch;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
 import java.awt.Dimension;
 import java.awt.Insets;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
 import javax.swing.JLabel;
 import javax.swing.JTextField;
 import java.awt.Component;
 import javax.swing.Box;
 
+@SuppressWarnings("serial")
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class SkillTab extends JPanel {
-	/**
-	 * 
-	 */
-	private static final long serialVersionUID = 1L;
-	JPanel skillPanel;
-	JTextField txtTotalSkillPoints;
-	JTextField txtClassSkills;
-	JTextField txtCrossClass;
+	
+	ObservableListener<JTextField, DDSkills> totalListener;
+	ObservableListener<JTextField, DDCharacter> classSkillListener;
+	ObservableListener<JTextField, DDCharacter> crossSkillListener;
+	ObservableListener<JPanel, DDCharacter> skillListener;
 
 	public SkillTab() {
 		setPreferredSize(new Dimension(600, 300));
@@ -60,7 +65,7 @@ public class SkillTab extends JPanel {
 		gbc_lblTotalSkillPoints.gridy = 0;
 		panel.add(lblTotalSkillPoints, gbc_lblTotalSkillPoints);
 		
-		txtTotalSkillPoints = new JTextField();
+		JTextField txtTotalSkillPoints = new JTextField();
 		txtTotalSkillPoints.setEditable(false);
 		GridBagConstraints gbc_txtTotalSkillPoints = new GridBagConstraints();
 		gbc_txtTotalSkillPoints.insets = new Insets(0, 0, 0, 5);
@@ -85,7 +90,7 @@ public class SkillTab extends JPanel {
 		gbc_lblMaxRanksClasscross.gridy = 0;
 		panel.add(lblMaxRanksClasscross, gbc_lblMaxRanksClasscross);
 		
-		txtClassSkills = new JTextField();
+		JTextField txtClassSkills = new JTextField();
 		txtClassSkills.setEditable(false);
 		GridBagConstraints gbc_txtClassSkills = new GridBagConstraints();
 		gbc_txtClassSkills.insets = new Insets(0, 0, 0, 5);
@@ -103,7 +108,7 @@ public class SkillTab extends JPanel {
 		gbc_label.gridy = 0;
 		panel.add(label, gbc_label);
 		
-		txtCrossClass = new JTextField();
+		JTextField txtCrossClass = new JTextField();
 		txtCrossClass.setEditable(false);
 		GridBagConstraints gbc_txtCrossClass = new GridBagConstraints();
 		gbc_txtCrossClass.fill = GridBagConstraints.HORIZONTAL;
@@ -119,24 +124,46 @@ public class SkillTab extends JPanel {
 		gbc_scrollPane.gridy = 1;
 		add(scrollPane, gbc_scrollPane);
 		
-		skillPanel = new JPanel();
+		JPanel skillPanel = new JPanel();
 		scrollPane.setViewportView(skillPanel);
 		skillPanel.setLayout(new VerticalLayout());
+		
+		totalListener = new ObservableListener<>(txtTotalSkillPoints, (c, v) -> {
+			final int totalPoints = v.getSkills().stream().mapToInt(DDSkill::getPointsSpent).sum();
+			c.setText(Integer.toString(totalPoints));
+		});
+		classSkillListener = new ObservableListener<>(txtClassSkills, (c, v) -> {
+			c.setText(Integer.toString(v.getLevel() + 3));
+		});
+		crossSkillListener = new ObservableListener<>(txtCrossClass, (c, v) -> {
+			c.setText(Integer.toString((v.getLevel() + 3)/2));
+		});
+		Map<String, SkillLine> lines = new TreeMap<>();
+		skillListener = new ObservableListener<>(skillPanel, (c, v) -> {
+			Stream<DDSkill> st = v.getSkills().getSkills().stream().filter(
+					sk -> sk.getPointsSpent() > 0 || !sk.isRequiresTraining());
+			st.forEach(sk -> lines.computeIfAbsent(sk.getName(), k -> new SkillLine(v, sk)));
+			skillPanel.removeAll();
+			lines.values().forEach(skillPanel::add);
+			revalidate();
+			repaint();
+		});
 	}
 	
 	public void setModel(final DDCharacter model) {
-		// TODO (sjaffe): Observables?
-		skillPanel.removeAll();
-		int[] totalPoints = {0};
-		final DDSkills skills = model.getSkills();
-		skills.getSkills().stream().forEach( skill -> {
-			skillPanel.add(new SkillLine(model, skill));
-			totalPoints[0] += skill.getPointsSpent();
-		});
-		txtTotalSkillPoints.setText(Integer.toString(totalPoints[0]));
-		int classSkill = model.getLevel() + 3;
-		txtClassSkills.setText(Integer.toString(classSkill));
-		txtCrossClass.setText(Integer.toString(classSkill / 2));
+		DDSkills skills = model.getSkills();
+		classSkillListener.setObserved(model);
+		crossSkillListener.setObserved(model);
+		totalListener.setObserved(skills);
+		skillListener.setObserved(model);
 	}
 	
+	@Override
+	public void removeNotify() {
+		super.removeNotify();
+		ObserverDispatch.unsubscribeAll(classSkillListener);
+		ObserverDispatch.unsubscribeAll(crossSkillListener);
+		ObserverDispatch.unsubscribeAll(totalListener);
+		ObserverDispatch.unsubscribeAll(skillListener);
+	}	
 }

+ 246 - 0
src/main/lombok/org/leumasjaffe/charsheet/view/skills/NormalSkillLevelUpLine.java

@@ -0,0 +1,246 @@
+package org.leumasjaffe.charsheet.view.skills;
+
+import org.leumasjaffe.charsheet.model.Ability;
+import org.leumasjaffe.charsheet.model.DDCharacter;
+import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.charsheet.model.skill.DDSkill;
+import org.leumasjaffe.charsheet.util.AbilityHelper;
+import org.leumasjaffe.format.StringHelper;
+import org.leumasjaffe.observer.IndirectObservableListener;
+import org.leumasjaffe.observer.ObserverDispatch;
+
+import lombok.AccessLevel;
+import lombok.Value;
+import lombok.experimental.FieldDefaults;
+
+import java.awt.GridBagLayout;
+import javax.swing.JCheckBox;
+import java.awt.GridBagConstraints;
+import javax.swing.JLabel;
+import java.awt.Insets;
+import java.util.Optional;
+import java.util.function.IntFunction;
+import java.awt.Dimension;
+import javax.swing.JTextField;
+import java.awt.Color;
+import javax.swing.border.MatteBorder;
+import javax.swing.SwingConstants;
+import java.awt.Component;
+import javax.swing.Box;
+import javax.swing.JButton;
+import java.awt.Font;
+
+@SuppressWarnings("serial")
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+class NormalSkillLevelUpLine extends SkillLevelUpLine {
+	boolean isClassSkill;
+	DDSkill skill;
+	IntValue current;
+	IndirectObservableListener<JTextField, TotalPacket> totalListener;
+	
+	@Value
+	private static final class TotalPacket {
+		Optional<Ability.Scores> ability;
+		DDSkill skill;
+		IntValue points;
+	}
+	
+	public NormalSkillLevelUpLine(final DDCharacter chara, final DDCharacterClass cclass, 
+			final DDSkill skill, IntValue pointsAvaliable) {
+		isClassSkill = cclass.isClassSkill(skill.getName());
+		this.skill = skill;
+		current = new IntValue(0);
+		final int pointsPerRank = isClassSkill ? 1 : 2;
+		final int maxPoints = (chara.getLevel() + 3) - skill.getRanks().value();
+		
+		setBorder(new MatteBorder(0, 0, 1, 0, new Color(0, 0, 0)));
+		setPreferredSize(new Dimension(480, 22));
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JCheckBox checkBoxIsClassSkill = new JCheckBox("");
+		checkBoxIsClassSkill.setToolTipText("Class Skill?");
+		checkBoxIsClassSkill.setSelected(cclass.isClassSkill(skill.getName()));
+		checkBoxIsClassSkill.setEnabled(false);
+		GridBagConstraints gbc_checkBoxIsClassSkill = new GridBagConstraints();
+		gbc_checkBoxIsClassSkill.insets = new Insets(1, 0, 0, 5);
+		gbc_checkBoxIsClassSkill.gridx = 0;
+		gbc_checkBoxIsClassSkill.gridy = 0;
+		add(checkBoxIsClassSkill, gbc_checkBoxIsClassSkill);
+		
+		JLabel lblName = new JLabel(skill.getName());
+		lblName.setToolTipText(skill.getName());
+		lblName.setMaximumSize(new Dimension(150, 14));
+		lblName.setMinimumSize(new Dimension(150, 14));
+		lblName.setPreferredSize(new Dimension(150, 14));
+		GridBagConstraints gbc_lblName = new GridBagConstraints();
+		gbc_lblName.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblName.insets = new Insets(1, 0, 0, 5);
+		gbc_lblName.gridx = 1;
+		gbc_lblName.gridy = 0;
+		add(lblName, gbc_lblName);
+		
+		JLabel lblAbil = new JLabel(skill.getAbility());
+		lblAbil.setMaximumSize(new Dimension(30, 14));
+		lblAbil.setMinimumSize(new Dimension(30, 14));
+		lblAbil.setPreferredSize(new Dimension(30, 14));
+		GridBagConstraints gbc_lblAbil = new GridBagConstraints();
+		gbc_lblAbil.insets = new Insets(1, 0, 0, 5);
+		gbc_lblAbil.anchor = GridBagConstraints.EAST;
+		gbc_lblAbil.gridx = 2;
+		gbc_lblAbil.gridy = 0;
+		add(lblAbil, gbc_lblAbil);
+		
+		JTextField total = new JTextField();
+		total.setToolTipText("Skill Modifier");
+		total.setHorizontalAlignment(SwingConstants.CENTER);
+		total.setEditable(false);
+		total.setMinimumSize(new Dimension(30, 20));
+		total.setMaximumSize(new Dimension(30, 20));
+		total.setPreferredSize(new Dimension(30, 20));
+		GridBagConstraints gbc_total = new GridBagConstraints();
+		gbc_total.insets = new Insets(1, 0, 0, 5);
+		gbc_total.fill = GridBagConstraints.HORIZONTAL;
+		gbc_total.gridx = 3;
+		gbc_total.gridy = 0;
+		add(total, gbc_total);
+		total.setColumns(10);
+		
+		JLabel label = new JLabel("=");
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.anchor = GridBagConstraints.EAST;
+		gbc_label.insets = new Insets(1, 0, 0, 5);
+		gbc_label.gridx = 4;
+		gbc_label.gridy = 0;
+		add(label, gbc_label);
+		
+		JTextField modifier = new JTextField();
+		modifier.setToolTipText("Ability Modifier");
+		modifier.setHorizontalAlignment(SwingConstants.CENTER);
+		modifier.setEditable(false);
+		modifier.setMinimumSize(new Dimension(30, 20));
+		modifier.setMaximumSize(new Dimension(30, 20));
+		modifier.setPreferredSize(new Dimension(30, 20));
+		GridBagConstraints gbc_modifier = new GridBagConstraints();
+		gbc_modifier.insets = new Insets(1, 0, 0, 5);
+		gbc_modifier.fill = GridBagConstraints.HORIZONTAL;
+		gbc_modifier.gridx = 5;
+		gbc_modifier.gridy = 0;
+		add(modifier, gbc_modifier);
+		modifier.setColumns(10);
+		
+		JLabel label_1 = new JLabel("+");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.insets = new Insets(1, 0, 0, 5);
+		gbc_label_1.gridx = 6;
+		gbc_label_1.gridy = 0;
+		add(label_1, gbc_label_1);
+		
+		JTextField ranks = new JTextField(StringHelper.toString(skill.getRanks()));
+		ranks.setToolTipText("Ranks");
+		ranks.setHorizontalAlignment(SwingConstants.CENTER);
+		ranks.setEditable(false);
+		ranks.setMinimumSize(new Dimension(30, 20));
+		ranks.setMaximumSize(new Dimension(30, 20));
+		ranks.setPreferredSize(new Dimension(30, 20));
+		GridBagConstraints gbc_ranks = new GridBagConstraints();
+		gbc_ranks.insets = new Insets(1, 0, 0, 5);
+		gbc_ranks.fill = GridBagConstraints.HORIZONTAL;
+		gbc_ranks.gridx = 7;
+		gbc_ranks.gridy = 0;
+		add(ranks, gbc_ranks);
+		ranks.setColumns(10);
+		
+		Component horizontalStrut = Box.createHorizontalStrut(10);
+		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
+		gbc_horizontalStrut.gridx = 8;
+		gbc_horizontalStrut.gridy = 0;
+		add(horizontalStrut, gbc_horizontalStrut);
+		
+		JButton plus = new JButton("+");
+		plus.setMargin(new Insets(2, 2, 2, 2));
+		plus.setFont(new Font("Tahoma", Font.PLAIN, 8));
+		plus.setPreferredSize(new Dimension(25, 19));
+		plus.setMinimumSize(new Dimension(25, 19));
+		GridBagConstraints gbc_plus = new GridBagConstraints();
+		gbc_plus.insets = new Insets(0, 0, 1, 5);
+		gbc_plus.gridx = 9;
+		gbc_plus.gridy = 0;
+		add(plus, gbc_plus);
+		
+		JButton minus = new JButton("-");
+		minus.setMargin(new Insets(2, 2, 2, 2));
+		minus.setFont(new Font("Tahoma", Font.PLAIN, 8));
+		minus.setMinimumSize(new Dimension(25, 19));
+		minus.setPreferredSize(new Dimension(25, 19));
+		GridBagConstraints gbc_minus = new GridBagConstraints();
+		gbc_minus.insets = new Insets(0, 0, 1, 5);
+		gbc_minus.gridx = 10;
+		gbc_minus.gridy = 0;
+		add(minus, gbc_minus);
+		
+		JTextField points = new JTextField();
+		points.setMinimumSize(new Dimension(30, 20));
+		points.setText("0");
+		points.setEditable(false);
+		GridBagConstraints gbc_points = new GridBagConstraints();
+		gbc_points.insets = new Insets(0, 0, 0, 5);
+		gbc_points.gridx = 11;
+		gbc_points.gridy = 0;
+		add(points, gbc_points);
+		points.setColumns(10);
+		
+		Component horizontalStrut_1 = Box.createHorizontalStrut(5);
+		GridBagConstraints gbc_horizontalStrut_1 = new GridBagConstraints();
+		gbc_horizontalStrut_1.gridx = 12;
+		gbc_horizontalStrut_1.gridy = 0;
+		add(horizontalStrut_1, gbc_horizontalStrut_1);
+
+		IntFunction<Void> lambda = (value) -> {
+			pointsAvaliable.value(pointsAvaliable.value() - (value * pointsPerRank));
+			current.value(current.value() + value);
+			points.setText(Integer.toString(current.value()));
+			ObserverDispatch.notifySubscribers(pointsAvaliable); // TODO (this)?
+			ObserverDispatch.notifySubscribers(current); // TODO (this)?
+			return null;
+		};
+		
+		plus.addActionListener((e) -> {
+			if (pointsAvaliable.value() >= pointsPerRank && current.value() < maxPoints) { lambda.apply(1); }
+		});
+		minus.addActionListener((e) -> {
+			if (current.value() > 0) { lambda.apply(-1); }
+		});
+
+		totalListener = new IndirectObservableListener<>(total, (c, p) -> {
+			final int skillRanks = p.skill.getRanks().value();
+			final int mod = p.ability.map(v -> v.baseModifier()).orElse(0);
+			c.setText(StringHelper.toString(skillRanks + mod + p.points.value()));
+		});
+		final Optional<Ability.Scores> ability = getAbility(chara, skill);
+		ability.ifPresent(v -> modifier.setText(StringHelper.toString(v.baseModifier())));
+		totalListener.setObserved(new TotalPacket(ability, skill, current), current, skill);
+	}
+
+	private Optional<Ability.Scores> getAbility(final DDCharacter chara, final DDSkill skill) {
+		if (skill.getAbility() == null || skill.getAbility().isEmpty()) { return Optional.empty(); }
+		else { return Optional.of(AbilityHelper.get(chara, skill)); }
+	}
+
+	public void applyChange() {
+		skill.spendPoints(current.value(), !isClassSkill);
+		ObserverDispatch.notifySubscribers(skill.getRanks()); // TODO (this)?
+	}
+	
+	@Override
+	public void removeNotify() {
+		super.removeNotify();
+		ObserverDispatch.unsubscribeAll(totalListener);
+	}
+}

+ 3 - 246
src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpLine.java

@@ -2,250 +2,7 @@ package org.leumasjaffe.charsheet.view.skills;
 
 import javax.swing.JPanel;
 
-import org.leumasjaffe.charsheet.model.Ability;
-import org.leumasjaffe.charsheet.model.DDCharacter;
-import org.leumasjaffe.charsheet.model.DDCharacterClass;
-import org.leumasjaffe.charsheet.model.observable.IntValue;
-import org.leumasjaffe.charsheet.model.skill.DDSkill;
-import org.leumasjaffe.charsheet.util.AbilityHelper;
-import org.leumasjaffe.format.StringHelper;
-import org.leumasjaffe.observer.IndirectObservableListener;
-import org.leumasjaffe.observer.ObserverDispatch;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.Value;
-import lombok.experimental.FieldDefaults;
-
-import java.awt.GridBagLayout;
-import javax.swing.JCheckBox;
-import java.awt.GridBagConstraints;
-import javax.swing.JLabel;
-import java.awt.Insets;
-import java.util.Optional;
-import java.util.function.IntFunction;
-import java.awt.Dimension;
-import javax.swing.JTextField;
-import java.awt.Color;
-import javax.swing.border.MatteBorder;
-import javax.swing.SwingConstants;
-import java.awt.Component;
-import javax.swing.Box;
-import javax.swing.JButton;
-import java.awt.Font;
-
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-class SkillLevelUpLine extends JPanel {
-	/**
-	 * 
-	 */
-	private static final long serialVersionUID = 1L;
-	boolean isClassSkill;
-	@Getter DDSkill skill;
-	IntValue current;
-	IndirectObservableListener<JTextField, TotalPacket> totalListener;
-	
-	@Value
-	private static final class TotalPacket {
-		Optional<Ability.Scores> ability;
-		DDSkill skill;
-		IntValue points;
-	}
-	
-	public SkillLevelUpLine(final DDCharacter chara, final DDCharacterClass cclass, final DDSkill skill, IntValue pointsAvaliable) {
-		isClassSkill = cclass.isClassSkill(skill.getName());
-		this.skill = skill;
-		current = new IntValue(0);
-		final int pointsPerRank = isClassSkill ? 1 : 2;
-		final int maxPoints = (chara.getLevel() + 3) - skill.getRanks().value();
-		
-		setBorder(new MatteBorder(0, 0, 1, 0, (Color) new Color(0, 0, 0)));
-		setPreferredSize(new Dimension(480, 22));
-		GridBagLayout gridBagLayout = new GridBagLayout();
-		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-		gridBagLayout.rowHeights = new int[]{0, 0};
-		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
-		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
-		setLayout(gridBagLayout);
-		
-		JCheckBox checkBoxIsClassSkill = new JCheckBox("");
-		checkBoxIsClassSkill.setToolTipText("Class Skill?");
-		checkBoxIsClassSkill.setSelected(cclass.isClassSkill(skill.getName()));
-		checkBoxIsClassSkill.setEnabled(false);
-		GridBagConstraints gbc_checkBoxIsClassSkill = new GridBagConstraints();
-		gbc_checkBoxIsClassSkill.insets = new Insets(1, 0, 0, 5);
-		gbc_checkBoxIsClassSkill.gridx = 0;
-		gbc_checkBoxIsClassSkill.gridy = 0;
-		add(checkBoxIsClassSkill, gbc_checkBoxIsClassSkill);
-		
-		JLabel lblName = new JLabel(skill.getName());
-		lblName.setMaximumSize(new Dimension(150, 14));
-		lblName.setMinimumSize(new Dimension(150, 14));
-		lblName.setPreferredSize(new Dimension(150, 14));
-		GridBagConstraints gbc_lblName = new GridBagConstraints();
-		gbc_lblName.fill = GridBagConstraints.HORIZONTAL;
-		gbc_lblName.insets = new Insets(1, 0, 0, 5);
-		gbc_lblName.gridx = 1;
-		gbc_lblName.gridy = 0;
-		add(lblName, gbc_lblName);
-		
-		JLabel lblAbil = new JLabel(skill.getAbility());
-		lblAbil.setMaximumSize(new Dimension(30, 14));
-		lblAbil.setMinimumSize(new Dimension(30, 14));
-		lblAbil.setPreferredSize(new Dimension(30, 14));
-		GridBagConstraints gbc_lblAbil = new GridBagConstraints();
-		gbc_lblAbil.insets = new Insets(1, 0, 0, 5);
-		gbc_lblAbil.anchor = GridBagConstraints.EAST;
-		gbc_lblAbil.gridx = 2;
-		gbc_lblAbil.gridy = 0;
-		add(lblAbil, gbc_lblAbil);
-		
-		JTextField total = new JTextField();
-		total.setToolTipText("Skill Modifier");
-		total.setHorizontalAlignment(SwingConstants.CENTER);
-		total.setEditable(false);
-		total.setMinimumSize(new Dimension(30, 20));
-		total.setMaximumSize(new Dimension(30, 20));
-		total.setPreferredSize(new Dimension(30, 20));
-		GridBagConstraints gbc_total = new GridBagConstraints();
-		gbc_total.insets = new Insets(1, 0, 0, 5);
-		gbc_total.fill = GridBagConstraints.HORIZONTAL;
-		gbc_total.gridx = 3;
-		gbc_total.gridy = 0;
-		add(total, gbc_total);
-		total.setColumns(10);
-		
-		JLabel label = new JLabel("=");
-		GridBagConstraints gbc_label = new GridBagConstraints();
-		gbc_label.anchor = GridBagConstraints.EAST;
-		gbc_label.insets = new Insets(1, 0, 0, 5);
-		gbc_label.gridx = 4;
-		gbc_label.gridy = 0;
-		add(label, gbc_label);
-		
-		JTextField modifier = new JTextField();
-		modifier.setToolTipText("Ability Modifier");
-		modifier.setHorizontalAlignment(SwingConstants.CENTER);
-		modifier.setEditable(false);
-		modifier.setMinimumSize(new Dimension(30, 20));
-		modifier.setMaximumSize(new Dimension(30, 20));
-		modifier.setPreferredSize(new Dimension(30, 20));
-		GridBagConstraints gbc_modifier = new GridBagConstraints();
-		gbc_modifier.insets = new Insets(1, 0, 0, 5);
-		gbc_modifier.fill = GridBagConstraints.HORIZONTAL;
-		gbc_modifier.gridx = 5;
-		gbc_modifier.gridy = 0;
-		add(modifier, gbc_modifier);
-		modifier.setColumns(10);
-		
-		JLabel label_1 = new JLabel("+");
-		GridBagConstraints gbc_label_1 = new GridBagConstraints();
-		gbc_label_1.anchor = GridBagConstraints.EAST;
-		gbc_label_1.insets = new Insets(1, 0, 0, 5);
-		gbc_label_1.gridx = 6;
-		gbc_label_1.gridy = 0;
-		add(label_1, gbc_label_1);
-		
-		JTextField ranks = new JTextField(StringHelper.toString(skill.getRanks()));
-		ranks.setToolTipText("Ranks");
-		ranks.setHorizontalAlignment(SwingConstants.CENTER);
-		ranks.setEditable(false);
-		ranks.setMinimumSize(new Dimension(30, 20));
-		ranks.setMaximumSize(new Dimension(30, 20));
-		ranks.setPreferredSize(new Dimension(30, 20));
-		GridBagConstraints gbc_ranks = new GridBagConstraints();
-		gbc_ranks.insets = new Insets(1, 0, 0, 5);
-		gbc_ranks.fill = GridBagConstraints.HORIZONTAL;
-		gbc_ranks.gridx = 7;
-		gbc_ranks.gridy = 0;
-		add(ranks, gbc_ranks);
-		ranks.setColumns(10);
-		
-		Component horizontalStrut = Box.createHorizontalStrut(20);
-		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
-		gbc_horizontalStrut.insets = new Insets(0, 0, 0, 5);
-		gbc_horizontalStrut.gridx = 8;
-		gbc_horizontalStrut.gridy = 0;
-		add(horizontalStrut, gbc_horizontalStrut);
-		
-		JButton plus = new JButton("+");
-		plus.setMargin(new Insets(2, 2, 2, 2));
-		plus.setFont(new Font("Tahoma", Font.PLAIN, 8));
-		plus.setPreferredSize(new Dimension(30, 19));
-		plus.setMinimumSize(new Dimension(30, 19));
-		GridBagConstraints gbc_plus = new GridBagConstraints();
-		gbc_plus.insets = new Insets(0, 0, 1, 5);
-		gbc_plus.gridx = 9;
-		gbc_plus.gridy = 0;
-		add(plus, gbc_plus);
-		
-		JButton minus = new JButton("-");
-		minus.setMargin(new Insets(2, 2, 2, 2));
-		minus.setFont(new Font("Tahoma", Font.PLAIN, 8));
-		minus.setMinimumSize(new Dimension(30, 19));
-		minus.setPreferredSize(new Dimension(30, 19));
-		GridBagConstraints gbc_minus = new GridBagConstraints();
-		gbc_minus.insets = new Insets(0, 0, 1, 5);
-		gbc_minus.gridx = 10;
-		gbc_minus.gridy = 0;
-		add(minus, gbc_minus);
-		
-		JTextField points = new JTextField();
-		points.setMinimumSize(new Dimension(30, 20));
-		points.setText("0");
-		points.setEditable(false);
-		GridBagConstraints gbc_points = new GridBagConstraints();
-		gbc_points.insets = new Insets(0, 0, 0, 5);
-		gbc_points.gridx = 11;
-		gbc_points.gridy = 0;
-		add(points, gbc_points);
-		points.setColumns(10);
-		
-		IntFunction<Void> lambda = (value) -> {
-			pointsAvaliable.value(pointsAvaliable.value() - (value * pointsPerRank));
-			current.value(current.value() + value);
-			points.setText(Integer.toString(current.value()));
-			ObserverDispatch.notifySubscribers(pointsAvaliable); // TODO (this)?
-			ObserverDispatch.notifySubscribers(current); // TODO (this)?
-			return null;
-		};
-		
-		plus.addActionListener((e) -> {
-			if (pointsAvaliable.value() >= pointsPerRank && current.value() < maxPoints) { lambda.apply(1); }
-		});
-		minus.addActionListener((e) -> {
-			if (current.value() > 0) { lambda.apply(-1); }
-		});
-
-		totalListener = new IndirectObservableListener<>(total, (c, p) -> {
-			final int skillRanks = p.skill.getRanks().value();
-			final int mod = p.ability.map(v -> v.baseModifier()).orElse(0);
-			c.setText(StringHelper.toString(skillRanks + mod + p.points.value()));
-		});
-
-		Component horizontalStrut_1 = Box.createHorizontalStrut(5);
-		GridBagConstraints gbc_horizontalStrut_1 = new GridBagConstraints();
-		gbc_horizontalStrut_1.gridx = 12;
-		gbc_horizontalStrut_1.gridy = 0;
-		add(horizontalStrut_1, gbc_horizontalStrut_1);
-		final Optional<Ability.Scores> ability = getAbility(chara, skill);
-		ability.ifPresent(v -> modifier.setText(StringHelper.toString(v.baseModifier())));
-		totalListener.setObserved(new TotalPacket(ability, skill, current), current, skill);
-	}
-
-	private Optional<Ability.Scores> getAbility(final DDCharacter chara, final DDSkill skill) {
-		if (skill.getAbility().isEmpty()) { return Optional.empty(); }
-		else { return Optional.of(AbilityHelper.get(chara, skill)); }
-	}
-
-	public void applyChange() {
-		skill.spendPoints(current.value(), !isClassSkill);
-		ObserverDispatch.notifySubscribers(skill.getRanks()); // TODO (this)?
-	}
-	
-	@Override
-	public void removeNotify() {
-		super.removeNotify();
-		ObserverDispatch.unsubscribeAll(totalListener);
-	}
+@SuppressWarnings("serial")
+abstract class SkillLevelUpLine extends JPanel {
+	abstract void applyChange();
 }

+ 46 - 16
src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpPanel.java

@@ -4,8 +4,9 @@ import java.awt.Dimension;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 import javax.swing.JLabel;
 import javax.swing.JPanel;
@@ -16,6 +17,7 @@ import org.jdesktop.swingx.VerticalLayout;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.charsheet.model.skill.DDSkill;
 import org.leumasjaffe.charsheet.model.skill.DDSkills;
 import org.leumasjaffe.observer.ObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
@@ -23,17 +25,29 @@ import org.leumasjaffe.observer.ObserverDispatch;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.experimental.FieldDefaults;
+import javax.swing.ScrollPaneConstants;
 
 @SuppressWarnings("serial")
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public abstract class SkillLevelUpPanel extends JPanel {
 	ObservableListener<JTextField, IntValue> purchaseListener;
+	ObservableListener<SkillLevelUpPanel, DDSkills> listener;
+	
 	@Getter(AccessLevel.PROTECTED) JPanel panel;
-	List<SkillLevelUpLine> lines;
+	JPanel skillPanel;
+	Map<String, SkillLevelUpLine> lines = new TreeMap<>();
+	
+	DDCharacter ddChara;
+	DDCharacterClass ddClass;
+	DDSkills skills;
+	IntValue pointsAvailable;
 
 	public SkillLevelUpPanel(final DDCharacter chara, final DDCharacterClass cclass) {
-		final IntValue pointsAvaliable = new IntValue(Math.max(1, cclass.getSkillPoints() + 
-				chara.getAbilities().getInt().baseModifier()));
+		this.ddChara = chara;
+		this.ddClass = cclass;
+		this.skills = ddChara.getSkills();
+		this.pointsAvailable = new IntValue(Math.max(1, ddClass.getSkillPoints() + 
+				ddChara.getAbilities().getInt().baseModifier()));
 		
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0};
@@ -75,41 +89,57 @@ public abstract class SkillLevelUpPanel extends JPanel {
 		pointsRemaining.setColumns(10);
 		
 		JScrollPane scrollPane = new JScrollPane();
+		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
 		GridBagConstraints gbc_scrollPane = new GridBagConstraints();
 		gbc_scrollPane.fill = GridBagConstraints.BOTH;
 		gbc_scrollPane.gridx = 0;
 		gbc_scrollPane.gridy = 1;
 		add(scrollPane, gbc_scrollPane);
 		
-		JPanel skillPanel = new JPanel();
-		skillPanel.setMinimumSize(new Dimension(300, 200));
+		skillPanel = new JPanel();
+		scrollPane.setPreferredSize(new Dimension(480, 300));
 		scrollPane.setViewportView(skillPanel);
 		skillPanel.setLayout(new VerticalLayout());
-				
-		lines = new ArrayList<>();
-		final DDSkills skills = chara.getSkills();
-		skills.getSkills().stream().forEach( skill -> {
-			SkillLevelUpLine line = new SkillLevelUpLine(chara, cclass, skill, pointsAvaliable);
-			skillPanel.add(line);
-			lines.add(line);
+		
+		listener = new ObservableListener<>(this, (c, v) -> {
+			v.getSkills().stream().forEach(skill -> {
+				c.lines.computeIfAbsent(skill.getName(), k -> createSkillLine(skill));
+			});
+			c.skillPanel.removeAll();
+			c.lines.values().forEach(c.skillPanel::add);
+			c.revalidate();
+			c.repaint();
 		});
+		listener.setObserved(skills);
 		
 		purchaseListener = new ObservableListener<>(pointsRemaining, (c, v) -> {
 			setIsReady(v.value() == 0);
 			c.setText(Integer.toString(v.value()));
 		});
-		purchaseListener.setObserved(pointsAvaliable);
+		purchaseListener.setObserved(pointsAvailable);
+	}
+
+	private SkillLevelUpLine createSkillLine(DDSkill skill) {
+		return skill.isWildcardSkill()
+				? new WildcardSkillLevelUpLine(ddChara, ddClass, skills, skill)
+				: new NormalSkillLevelUpLine(ddChara, ddClass, skill, pointsAvailable);
 	}
 	
 	protected abstract void setIsReady(boolean b);
 	
 	public void commitAllChanges() {
-		lines.forEach(SkillLevelUpLine::applyChange);
+		lines.values().forEach(SkillLevelUpLine::applyChange);
+		skills.getSkills().stream().filter(
+				sk -> sk.isFromWildcardSkill() && sk.getPointsSpent() == 0)
+		.collect(Collectors.toList()).forEach(skills::removeSkill);
+		ObserverDispatch.notifySubscribers(ddChara);
+		listener.notifySubscribers(skills);
 	}
 	
 	@Override
 	public void removeNotify() {
 		super.removeNotify();
+		ObserverDispatch.unsubscribeAll(listener);
 		ObserverDispatch.unsubscribeAll(purchaseListener);
 	}
 }

+ 216 - 0
src/main/lombok/org/leumasjaffe/charsheet/view/skills/WildcardSkillLevelUpLine.java

@@ -0,0 +1,216 @@
+package org.leumasjaffe.charsheet.view.skills;
+
+import org.leumasjaffe.charsheet.model.Ability;
+import org.leumasjaffe.charsheet.model.DDCharacter;
+import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.charsheet.model.skill.DDSkill;
+import org.leumasjaffe.charsheet.model.skill.DDSkills;
+import org.leumasjaffe.charsheet.util.AbilityHelper;
+import org.leumasjaffe.observer.ObserverDispatch;
+
+import lombok.AccessLevel;
+import lombok.Value;
+import lombok.experimental.FieldDefaults;
+
+import java.awt.GridBagLayout;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import java.awt.GridBagConstraints;
+import javax.swing.JLabel;
+import java.awt.Insets;
+import java.util.Optional;
+import java.awt.Dimension;
+import java.awt.Font;
+
+import javax.swing.JTextField;
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.border.MatteBorder;
+import javax.swing.SwingConstants;
+import javax.swing.JPanel;
+
+@SuppressWarnings("serial")
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+class WildcardSkillLevelUpLine extends SkillLevelUpLine {	
+	@Value
+	private static final class TotalPacket {
+		Optional<Ability.Scores> ability;
+		DDSkill skill;
+		IntValue points;
+	}
+	
+	public WildcardSkillLevelUpLine(final DDCharacter chara, final DDCharacterClass cclass, 
+			final DDSkills skillList, final DDSkill skill) {
+		
+		setBorder(new MatteBorder(0, 0, 1, 0, (Color) new Color(0, 0, 0)));
+		setPreferredSize(new Dimension(480, 22));
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JCheckBox checkBoxIsClassSkill = new JCheckBox("");
+		checkBoxIsClassSkill.setToolTipText("Class Skill?");
+		checkBoxIsClassSkill.setSelected(cclass.isClassSkill(skill.getName()));
+		checkBoxIsClassSkill.setEnabled(false);
+		GridBagConstraints gbc_checkBoxIsClassSkill = new GridBagConstraints();
+		gbc_checkBoxIsClassSkill.insets = new Insets(1, 0, 0, 5);
+		gbc_checkBoxIsClassSkill.gridx = 0;
+		gbc_checkBoxIsClassSkill.gridy = 0;
+		add(checkBoxIsClassSkill, gbc_checkBoxIsClassSkill);
+		
+		JPanel panel = new JPanel();
+		GridBagConstraints gbc_panel = new GridBagConstraints();
+		panel.setMaximumSize(new Dimension(150, 14));
+		panel.setMinimumSize(new Dimension(150, 14));
+		panel.setPreferredSize(new Dimension(150, 14));
+		gbc_panel.insets = new Insets(1, 0, 0, 5);
+		gbc_panel.fill = GridBagConstraints.BOTH;
+		gbc_panel.gridx = 1;
+		gbc_panel.gridy = 0;
+		add(panel, gbc_panel);
+		GridBagLayout gbl_panel = new GridBagLayout();
+		gbl_panel.columnWidths = new int[]{0, 0, 0, 0};
+		gbl_panel.rowHeights = new int[]{0, 0};
+		gbl_panel.columnWeights = new double[]{0.0, 1.0, 0.0, Double.MIN_VALUE};
+		gbl_panel.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		panel.setLayout(gbl_panel);
+				
+		final String skillLead = skill.getName().replaceAll("\\(.*", "(");
+		JLabel lblName = new JLabel(skillLead);
+		GridBagConstraints gbc_lblName = new GridBagConstraints();
+		gbc_lblName.anchor = GridBagConstraints.EAST;
+		gbc_lblName.gridx = 0;
+		gbc_lblName.gridy = 0;
+		panel.add(lblName, gbc_lblName);
+		
+		JTextField textField = new JTextField();
+		textField.setMinimumSize(new Dimension(10, 20));
+		textField.setPreferredSize(new Dimension(10, 20));
+		GridBagConstraints gbc_textField = new GridBagConstraints();
+		gbc_textField.fill = GridBagConstraints.HORIZONTAL;
+		gbc_textField.gridx = 1;
+		gbc_textField.gridy = 0;
+		panel.add(textField, gbc_textField);
+		textField.setColumns(10);
+		
+		JLabel lblName2 = new JLabel(")");
+		GridBagConstraints gbc_lblName2 = new GridBagConstraints();
+		gbc_lblName2.gridx = 2;
+		gbc_lblName2.gridy = 0;
+		panel.add(lblName2, gbc_lblName2);
+		
+		JLabel lblAbil = new JLabel(skill.getAbility());
+		lblAbil.setMaximumSize(new Dimension(30, 14));
+		lblAbil.setMinimumSize(new Dimension(30, 14));
+		lblAbil.setPreferredSize(new Dimension(30, 14));
+		GridBagConstraints gbc_lblAbil = new GridBagConstraints();
+		gbc_lblAbil.insets = new Insets(1, 0, 0, 5);
+		gbc_lblAbil.anchor = GridBagConstraints.EAST;
+		gbc_lblAbil.gridx = 2;
+		gbc_lblAbil.gridy = 0;
+		add(lblAbil, gbc_lblAbil);
+		
+		String modStr = Integer.toString(getAbility(chara, skill).map(Ability.Scores::modifier).orElse(0));
+		JTextField total = new JTextField(modStr);
+		total.setToolTipText("Skill Modifier");
+		total.setHorizontalAlignment(SwingConstants.CENTER);
+		total.setEditable(false);
+		total.setMinimumSize(new Dimension(30, 20));
+		total.setMaximumSize(new Dimension(30, 20));
+		total.setPreferredSize(new Dimension(30, 20));
+		GridBagConstraints gbc_total = new GridBagConstraints();
+		gbc_total.insets = new Insets(1, 0, 0, 5);
+		gbc_total.fill = GridBagConstraints.HORIZONTAL;
+		gbc_total.gridx = 3;
+		gbc_total.gridy = 0;
+		add(total, gbc_total);
+		total.setColumns(10);
+		
+		JLabel label = new JLabel("=");
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.anchor = GridBagConstraints.EAST;
+		gbc_label.insets = new Insets(1, 0, 0, 5);
+		gbc_label.gridx = 4;
+		gbc_label.gridy = 0;
+		add(label, gbc_label);
+		
+		JTextField modifier = new JTextField(modStr);
+		modifier.setToolTipText("Ability Modifier");
+		modifier.setHorizontalAlignment(SwingConstants.CENTER);
+		modifier.setEditable(false);
+		modifier.setMinimumSize(new Dimension(30, 20));
+		modifier.setMaximumSize(new Dimension(30, 20));
+		modifier.setPreferredSize(new Dimension(30, 20));
+		GridBagConstraints gbc_modifier = new GridBagConstraints();
+		gbc_modifier.insets = new Insets(1, 0, 0, 5);
+		gbc_modifier.fill = GridBagConstraints.HORIZONTAL;
+		gbc_modifier.gridx = 5;
+		gbc_modifier.gridy = 0;
+		add(modifier, gbc_modifier);
+		modifier.setColumns(10);
+		
+		JLabel label_1 = new JLabel("+");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.insets = new Insets(1, 0, 0, 5);
+		gbc_label_1.gridx = 6;
+		gbc_label_1.gridy = 0;
+		add(label_1, gbc_label_1);
+		
+		JTextField ranks = new JTextField("0");
+		ranks.setToolTipText("Ranks");
+		ranks.setHorizontalAlignment(SwingConstants.CENTER);
+		ranks.setEditable(false);
+		ranks.setMinimumSize(new Dimension(30, 20));
+		ranks.setMaximumSize(new Dimension(30, 20));
+		ranks.setPreferredSize(new Dimension(30, 20));
+		GridBagConstraints gbc_ranks = new GridBagConstraints();
+		gbc_ranks.insets = new Insets(1, 0, 0, 5);
+		gbc_ranks.fill = GridBagConstraints.HORIZONTAL;
+		gbc_ranks.gridx = 7;
+		gbc_ranks.gridy = 0;
+		add(ranks, gbc_ranks);
+		ranks.setColumns(10);
+		
+		Component horizontalStrut = Box.createHorizontalStrut(10);
+		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
+		gbc_horizontalStrut.gridx = 8;
+		gbc_horizontalStrut.gridy = 0;
+		add(horizontalStrut, gbc_horizontalStrut);
+		
+		JButton addSkill = new JButton("Add Skill");
+		addSkill.setMargin(new Insets(2, 2, 2, 2));
+		addSkill.setFont(new Font("Tahoma", Font.PLAIN, 8));
+		addSkill.setPreferredSize(new Dimension(55, 19));
+		addSkill.setMinimumSize(new Dimension(55, 19));
+		GridBagConstraints gbc_plus = new GridBagConstraints();
+		gbc_plus.insets = new Insets(1, 0, 0, 5);
+		gbc_plus.gridx = 9;
+		gbc_plus.gridy = 0;
+		add(addSkill, gbc_plus);
+		
+		addSkill.addActionListener(e -> {
+			final String input = textField.getText();
+			if (input.isEmpty()) { return; }
+			textField.setText("");
+			DDSkill newSkill = skillList.getSkill(skillLead + input + ")");
+			if (newSkill.getPointsSpent() == 0) { // Don't notify if I typed a skill I already know
+				ObserverDispatch.notifySubscribers(skillList);
+			}
+		});
+	}
+
+	private Optional<Ability.Scores> getAbility(final DDCharacter chara, final DDSkill skill) {
+		if (skill.getAbility() == null || skill.getAbility().isEmpty()) { return Optional.empty(); }
+		else { return Optional.of(AbilityHelper.get(chara, skill)); }
+	}
+
+	public void applyChange() {	}
+}