소스 검색

Large conversion from using bad reload functions to using good Observables.
Model:
- Convert DDSpellbook into an Observable, replacing interfaces with abstract classes.
- Convert DDCharacterClass to an Observable
- Convert DDCharacterClass::level to an IntValue (Observable)
View:
- ClassTab::experienceField now Listens to DDCharacter::experience
- ClassTab::levelField now Listends to DDCharacterClass::level
- SpellLevelPanel and SpellsPerDayHeader now listen to the DDSpellbook
- Reload functions have been overwritten by ObserverListener behaviors.
- SpellPanel listens to ability score, DDCharacterClass::level, and DDSpellbook
- Also, SpellPanel now won't replace it's entire contents every time it refreshes.
- SpellMenu now calls notifySubscribers instead of that awful getParent chain to call reload.
- SkillLevelUpLine is now simplified in its format
- Removed the reload function from D20Sheet, instead invoke a notify on entire tree using ObserverHelper

Sam Jaffe 8 년 전
부모
커밋
cea13bc653

+ 1 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacter.java

@@ -90,6 +90,6 @@ public class DDCharacter {
 	}
 
 	public int getLevel() {
-		return classes.stream().mapToInt(DDCharacterClass::getLevel).sum();
+		return classes.stream().map(DDCharacterClass::getLevel).mapToInt(IntValue::value).sum();
 	}
 }

+ 11 - 8
src/main/lombok/org/leumasjaffe/charsheet/model/DDCharacterClass.java

@@ -4,17 +4,20 @@ import java.util.List;
 import java.util.Optional;
 
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.observer.Observable;
 
 import lombok.AccessLevel;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.experimental.Delegate;
 import lombok.experimental.FieldDefaults;
-import lombok.experimental.NonFinal;
 
 @Data
+@EqualsAndHashCode(callSuper=false)
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class DDCharacterClass implements Comparable<DDCharacterClass> {
+public class DDCharacterClass extends Observable implements Comparable<DDCharacterClass> {
 	private static final class Reference {
 		DDClass base;
 		
@@ -40,7 +43,7 @@ public class DDCharacterClass implements Comparable<DDCharacterClass> {
 		}
 	}
 
-	@NonFinal int level;
+	IntValue level;
 //	@NonNull List<Integer> healthRolls;
 	@Delegate @Getter(AccessLevel.NONE) Reference name;
 	
@@ -55,19 +58,19 @@ public class DDCharacterClass implements Comparable<DDCharacterClass> {
 	}
 
 	public int getBab() {
-		return name.base.getBab().getBonus(level);
+		return name.base.getBab().getBonus(level.value());
 	}
 	
 	public int getFort() {
-		return name.base.getFort().getBonus(level);
+		return name.base.getFort().getBonus(level.value());
 	}
 	
 	public int getRef() {
-		return name.base.getRef().getBonus(level);
+		return name.base.getRef().getBonus(level.value());
 	}
 	
 	public int getWill() {
-		return name.base.getWill().getBonus(level);
+		return name.base.getWill().getBonus(level.value());
 	}
 	
 	public boolean isClassSkill(final String skill) {
@@ -81,7 +84,7 @@ public class DDCharacterClass implements Comparable<DDCharacterClass> {
 	public int getHighestSpellLevel() {
 		// TODO: Bonus levels to spellsKnown/spellsPerDay?
 		// TODO: Bonus spellsPerDay for high ability scores
-		final List<Integer> list = getProto().getSpells().get().getPerDay().get(getLevel()-1);
+		final List<Integer> list = getProto().getSpells().get().getPerDay().get(getLevel().value()-1);
 		final int level = list.size() - 1;
 		return list.get(level) == 0 ? level : level + 1;
 	}

+ 12 - 10
src/main/lombok/org/leumasjaffe/charsheet/model/magic/DDSpellbook.java

@@ -3,31 +3,33 @@ package org.leumasjaffe.charsheet.model.magic;
 import java.util.Collection;
 import java.util.List;
 
+import org.leumasjaffe.observer.Observable;
+
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 import lombok.NonNull;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
-public interface DDSpellbook {
-	@NonNull Collection<DDSpell> spellsKnownAtLevel( int level );
-	@NonNull List<DDSpell> spellsPreparedAtLevel( int level );
+public abstract class DDSpellbook extends Observable {
+	@NonNull public abstract Collection<DDSpell> spellsKnownAtLevel( int level );
+	@NonNull public abstract List<DDSpell> spellsPreparedAtLevel( int level );
 	
-	@NonNull default List<DDSpell> getSpellsPreparedPreviouslyForLevel(int level) { return spellsPreparedAtLevel(level); }	
+	@NonNull public List<DDSpell> getSpellsPreparedPreviouslyForLevel(int level) { return spellsPreparedAtLevel(level); }	
 
 
-	default boolean preparesSpells() { return false; }
+	public boolean preparesSpells() { return false; }
 	
-	default int numSpellsKnownAtLevel( int level ) {
+	public int numSpellsKnownAtLevel( int level ) {
 		return spellsKnownAtLevel( level ).size();
 	}
 	
-	int numSpellsPerDayAtLevel( int level );
+	public abstract int numSpellsPerDayAtLevel( int level );
 	
-	default int numSpellsPerDayRemainingAtLevel(int level) {
+	public int numSpellsPerDayRemainingAtLevel(int level) {
 		return spellsPreparedAtLevel( level ).size();
 	}
 	
-	void castSpell( int level, final DDSpell spell );
+	public abstract void castSpell( int level, final DDSpell spell );
 	
-	void prepareSpells(int level, List<DDSpell> spells);
+	public abstract void prepareSpells(int level, List<DDSpell> spells);
 }

+ 1 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/magic/impl/Inspired.java

@@ -16,7 +16,7 @@ import lombok.experimental.NonFinal;
 
 @AllArgsConstructor
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class Inspired implements Prepared {
+public class Inspired extends Prepared {
 	@AllArgsConstructor
 	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 	private static class Level {

+ 3 - 3
src/main/lombok/org/leumasjaffe/charsheet/model/magic/impl/Prepared.java

@@ -7,9 +7,9 @@ import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 
 import lombok.NonNull;
 
-public interface Prepared extends DDSpellbook {
+public abstract class Prepared extends DDSpellbook {
 	@Override
-	default boolean preparesSpells() { return true; }
+	public boolean preparesSpells() { return true; }
 
-	@NonNull List<DDSpell> getSpellsPreparedPreviouslyForLevel(int level);	
+	@NonNull public abstract List<DDSpell> getSpellsPreparedPreviouslyForLevel(int level);	
 }

+ 1 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/magic/impl/Researched.java

@@ -15,7 +15,7 @@ import lombok.experimental.NonFinal;
 
 @AllArgsConstructor
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class Researched implements Prepared {
+public class Researched extends Prepared {
 	@AllArgsConstructor
 	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 	private static class Level {

+ 1 - 1
src/main/lombok/org/leumasjaffe/charsheet/model/magic/impl/Spontaneous.java

@@ -16,7 +16,7 @@ import lombok.experimental.NonFinal;
 
 @AllArgsConstructor
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class Spontaneous implements DDSpellbook {
+public class Spontaneous extends DDSpellbook {
 	@AllArgsConstructor
 	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 	private static class Level {

+ 14 - 0
src/main/lombok/org/leumasjaffe/charsheet/observer/ObserverHelper.java

@@ -0,0 +1,14 @@
+package org.leumasjaffe.charsheet.observer;
+
+import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.observer.ObserverDispatch;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class ObserverHelper {
+	public void notifyObservableHierarchy(final DDCharacterClass dclass, final Object src) {
+		ObserverDispatch.notifySubscribers(dclass, src);
+		dclass.getSpellBook().ifPresent(sb -> ObserverDispatch.notifySubscribers(sb, src));
+	}
+}

+ 12 - 4
src/main/lombok/org/leumasjaffe/charsheet/view/ClassTab.java

@@ -9,8 +9,10 @@ import javax.swing.JTextField;
 
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
 import org.leumasjaffe.charsheet.util.StringHelper;
 import org.leumasjaffe.charsheet.view.magic.SpellPanel;
+import org.leumasjaffe.observer.ObservableListener;
 
 import java.awt.GridBagConstraints;
 import java.awt.Insets;
@@ -28,7 +30,9 @@ public class ClassTab extends JPanel {
 	private static final long serialVersionUID = 1L;
 
 	String title;
-	private JTextField experienceField;
+
+	ObservableListener<JTextField, IntValue> levelListener;
+	ObservableListener<JTextField, IntValue> expListener;
 	
 	public ClassTab(DDCharacter chara, DDCharacterClass model) {
 		this.title = model.getName();
@@ -107,7 +111,9 @@ public class ClassTab extends JPanel {
 		header.add(levelField, gbc_levelField);
 		levelField.setEditable(false);
 		levelField.setColumns(10);
-		levelField.setText(StringHelper.toString(model.getLevel()));
+		levelListener = new ObservableListener<>(levelField, 
+				(c, t) -> levelField.setText(StringHelper.toString(t.value())));
+		levelListener.setObserved(model.getLevel());
 		
 		Component horizontalStrut_1 = Box.createHorizontalStrut(20);
 		GridBagConstraints gbc_horizontalStrut_1 = new GridBagConstraints();
@@ -125,8 +131,7 @@ public class ClassTab extends JPanel {
 		gbc_lblExperience.gridy = 0;
 		header.add(lblExperience, gbc_lblExperience);
 		
-		experienceField = new JTextField();
-		experienceField.setText(Integer.toString(chara.getExperience().value()));
+		JTextField experienceField = new JTextField();
 		experienceField.setEditable(false);
 		experienceField.setColumns(10);
 		GridBagConstraints gbc_experienceField = new GridBagConstraints();
@@ -135,6 +140,9 @@ public class ClassTab extends JPanel {
 		gbc_experienceField.gridx = 7;
 		gbc_experienceField.gridy = 0;
 		header.add(experienceField, gbc_experienceField);
+		expListener = new ObservableListener<>(experienceField, 
+				(c, v) -> c.setText(Integer.toString(v.value())));
+		expListener.setObserved(chara.getExperience());
 		
 		JLabel label = new JLabel("/");
 		GridBagConstraints gbc_label = new GridBagConstraints();

+ 5 - 6
src/main/lombok/org/leumasjaffe/charsheet/view/D20Sheet.java

@@ -23,6 +23,7 @@ import javax.swing.UIManager;
 
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.observer.ObserverHelper;
 import org.leumasjaffe.charsheet.view.builders.DialogBuilder;
 import org.leumasjaffe.charsheet.view.dev.DeveloperMenu;
 
@@ -134,7 +135,9 @@ public class D20Sheet extends JFrame {
 				});
 			}
 			// Step N: regenerate spellbooks
-			reload();
+			for (DDCharacterClass dclass : model.getClasses()) {
+				ObserverHelper.notifyObservableHierarchy(dclass, this);
+			}
 		});
 		mnSession.add(mntmTakeRest);
 		
@@ -179,11 +182,7 @@ public class D20Sheet extends JFrame {
 			mapper.writeValue(currentlyOpenFile, model);
 		}
 	}
-	
-	public void reload() {
-		setModel(this.model); // TODO: make this not awful
-	}
-	
+		
 	private void setModel(DDCharacter model) {
 		classTabs.clear();
 		this.model = model;

+ 6 - 3
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellInfoPanel.java

@@ -14,6 +14,7 @@ import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.DDSpell.Component;
 import org.leumasjaffe.charsheet.model.magic.Source;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
 
 import javax.swing.JScrollPane;
 import javax.swing.JLabel;
@@ -32,6 +33,8 @@ class SpellInfoPanel extends JPanel {
 	private static final long serialVersionUID = 1L;
 
 	public SpellInfoPanel(DDCharacterClass dclass, final DDSpell spell) {
+		final IntValue classLevel = dclass.getLevel();
+		
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0};
 		gridBagLayout.rowHeights = new int[]{0, 0, 0};
@@ -191,7 +194,7 @@ class SpellInfoPanel extends JPanel {
 		gbc_lblRange.gridy = 3;
 		panel.add(lblRange, gbc_lblRange);
 		
-		JTextField range = new JTextField(spell.getRange().getResolved(dclass.getLevel()));
+		JTextField range = new JTextField(spell.getRange().getResolved(classLevel.value()));
 		range.setToolTipText(spell.getRange().toString());
 		range.setEditable(false);
 		range.setColumns(10);
@@ -230,7 +233,7 @@ class SpellInfoPanel extends JPanel {
 		gbc_lblEffect.gridy = 4;
 		panel.add(lblEffect, gbc_lblEffect);
 		
-		JTextField effect = new JTextField(asString(spell.getEffect(), e -> e.getResolved(dclass.getLevel())));
+		JTextField effect = new JTextField(asString(spell.getEffect(), e -> e.getResolved(classLevel.value())));
 		effect.setToolTipText(asString(spell.getEffect()));
 		effect.setEditable(false);
 		effect.setColumns(10);
@@ -269,7 +272,7 @@ class SpellInfoPanel extends JPanel {
 		gbc_lblDuration.gridy = 5;
 		panel.add(lblDuration, gbc_lblDuration);
 		
-		JTextField duration = new JTextField(spell.getDuration().getResolved(dclass.getLevel()));
+		JTextField duration = new JTextField(spell.getDuration().getResolved(classLevel.value()));
 		duration.setToolTipText(asString(spell.getDuration()));
 		duration.setEditable(false);
 		duration.setColumns(10);

+ 21 - 16
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPanel.java

@@ -6,9 +6,13 @@ import java.awt.Component;
 import javax.swing.Box;
 import java.awt.GridBagConstraints;
 import java.awt.Insets;
+import java.util.List;
 
 import org.jdesktop.swingx.VerticalLayout;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.magic.DDSpell;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.observer.IndirectObservableListener;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -19,14 +23,12 @@ class SpellLevelPanel extends JPanel {
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
-	JPanel panel;
-	DDCharacterClass dclass;
-	int level;
+	
+	IndirectObservableListener<JPanel, DDCharacterClass> listener;
+
+	protected SpellLevelPanel(JPanel header, DDCharacterClass dclass, int level) {		
+		final List<DDSpell> spells = dclass.getSpellBook().get().spellsPreparedAtLevel(level);
 
-	public SpellLevelPanel(JPanel header, DDCharacterClass dclass, int level) {
-		this.dclass = dclass;
-		this.level = level;
-		
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0, 0};
 		gridBagLayout.rowHeights = new int[]{0, 0, 0, 0};
@@ -42,14 +44,13 @@ class SpellLevelPanel extends JPanel {
 		gbc_panel_1.gridy = 0;
 		add(header, gbc_panel_1);
 		
-		panel = new JPanel(new VerticalLayout());
+		JPanel panel = new JPanel(new VerticalLayout());
 		GridBagConstraints gbc_panel = new GridBagConstraints();
 		gbc_panel.insets = new Insets(0, 0, 5, 0);
 		gbc_panel.fill = GridBagConstraints.BOTH;
 		gbc_panel.gridx = 1;
 		gbc_panel.gridy = 1;
 		add(panel, gbc_panel);
-		reload();
 		
 		Component horizontalStrut = Box.createHorizontalStrut(20);
 		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
@@ -57,14 +58,18 @@ class SpellLevelPanel extends JPanel {
 		gbc_horizontalStrut.gridx = 0;
 		gbc_horizontalStrut.gridy = 2;
 		add(horizontalStrut, gbc_horizontalStrut);
+		
+		listener = new IndirectObservableListener<>(panel, (c, v) -> { 
+			c.removeAll();
+			spells.forEach(spell -> c.add(new SpellLine(v, spell, isCastableFromHere())));
+			c.repaint();
+		});
+		listener.setObserved(dclass, dclass.getSpellBook().get());
 	}
 	
-	public boolean isCastableFromHere() { return false; }
-
-	protected void reload() {
-		panel.removeAll();
-		dclass.getSpellBook().get().spellsPreparedAtLevel(level).forEach(spell -> panel.add(new SpellLine(this.dclass, spell, isCastableFromHere())));
-		panel.repaint();
+	public SpellLevelPanel(DDCharacterClass dclass, int level, IntValue ability) {
+		this(new SpellsKnownHeader(level, dclass.getSpellBook().get(), ability), dclass, level);
 	}
-
+	
+	public boolean isCastableFromHere() { return false; }
 }

+ 3 - 10
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellLevelPerDayPanel.java

@@ -1,6 +1,7 @@
 package org.leumasjaffe.charsheet.view.magic;
 
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
+import org.leumasjaffe.charsheet.model.observable.IntValue;
 
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
@@ -12,18 +13,10 @@ class SpellLevelPerDayPanel extends SpellLevelPanel {
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
-
-	SpellsPerDayHeader header;
 	
-	public SpellLevelPerDayPanel(SpellsPerDayHeader header, DDCharacterClass dclass, int level) {
-		super(header, dclass, level);
-		this.header = header;
+	public SpellLevelPerDayPanel(DDCharacterClass dclass, int level, IntValue ability) {
+		super(new SpellsPerDayHeader(level, dclass.getSpellBook().get(), ability), dclass, level);
 	}
 	
 	public boolean isCastableFromHere() { return true; }
-
-	public void reload() {
-		if (header != null) { header.reload(); }
-		super.reload();
-	}
 }

+ 2 - 1
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellMenu.java

@@ -5,6 +5,7 @@ import javax.swing.JPopupMenu;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
 import org.leumasjaffe.charsheet.model.magic.DDSpell;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
+import org.leumasjaffe.observer.ObserverDispatch;
 
 import javax.swing.JFrame;
 import javax.swing.JMenuItem;
@@ -38,7 +39,7 @@ class SpellMenu extends JPopupMenu {
 					return;
 				}
 				book.castSpell(spellLevel, spell);
-				((SpellLevelPerDayPanel) this.getInvoker().getParent().getParent()).reload();
+				ObserverDispatch.notifySubscribers(book, null);
 			});
 			add(mntmCast);
 		}

+ 33 - 24
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellPanel.java

@@ -12,12 +12,13 @@ import org.jdesktop.swingx.VerticalLayout;
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.DDCharacter;
 import org.leumasjaffe.charsheet.model.DDCharacterClass;
-import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
-import org.leumasjaffe.function.VoidVoidFunction;
+import org.leumasjaffe.observer.IndirectObservableListener;
 
 import lombok.AccessLevel;
+import lombok.Value;
 import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
 
 import java.awt.GridBagConstraints;
 
@@ -28,11 +29,22 @@ public class SpellPanel extends JPanel {
 	 */
 	private static final long serialVersionUID = 1L;
 	DDCharacterClass dclass;
-	VoidVoidFunction _reload;
+	@NonFinal int previousHighestSpellLevel = 0;
+	IndirectObservableListener<Fields, Packet> listener;
+	
+	@Value
+	private static final class Fields {
+		JPanel prepared, known;
+	}
+	
+	@Value
+	private static final class Packet {
+		DDCharacterClass dclass;
+		IntValue ability;
+	}
 
 	public SpellPanel(DDCharacter chara, final DDCharacterClass cclass) {
 		dclass = cclass;
-		final DDSpellbook model = cclass.getSpellBook().get();
 		
 		GridBagLayout gridBagLayout = new GridBagLayout();
 		gridBagLayout.columnWidths = new int[]{0, 0};
@@ -54,30 +66,27 @@ public class SpellPanel extends JPanel {
 		JScrollPane knownPane = new JScrollPane();
 		spellsPane.addTab("Known", null, knownPane, "Spells the player knows for this class");
 		
-		final IntValue value = Ability.fields.get(dclass.getProto().getSpells().get().getAbility()).apply(chara.getAbilities().getBase());
-		_reload = () -> {
-			generateSpellTree((l) -> new SpellLevelPerDayPanel(new SpellsPerDayHeader(l, model, value), dclass, l),
-					preparedPane);
-			generateSpellTree((l) -> new SpellLevelPanel(new SpellsKnownHeader(l, model, value), dclass, l), 
-					knownPane);
-		};
-		reload();
+		final IntValue ability = Ability.fields.get(dclass.getProto().getSpells().get().getAbility()).apply(chara.getAbilities().getBase());
+		final JPanel prepared = new JPanel(new VerticalLayout());
+		preparedPane.setViewportView(prepared);
+		final JPanel known = new JPanel(new VerticalLayout());
+		knownPane.setViewportView(known);
+		
+		listener = new IndirectObservableListener<>(
+				new Fields(prepared, known), 
+				(c, p) -> {
+					generateSpellTree(c.prepared, (l) -> new SpellLevelPerDayPanel(p.dclass, l, p.ability));
+					generateSpellTree(c.known, (l) -> new SpellLevelPanel(p.dclass, l, p.ability));
+					previousHighestSpellLevel = dclass.getHighestSpellLevel();
+				});
+		listener.setObserved(new Packet(dclass, ability), ability, dclass.getLevel(), dclass.getSpellBook().get());
 	}
 	
-	public void reload() {
-		_reload.apply();
-	}
-
-	private void generateSpellTree(final Function<Integer, JPanel> getPanel, final JScrollPane preparedPane) {
-		JPanel root = new JPanel();
-		root.setLayout(new VerticalLayout());
-		
-		for (int i = 0; i < dclass.getHighestSpellLevel(); ++i) {
-			if (dclass.getSpellBook().get().numSpellsKnownAtLevel(i) == 0) continue;
+	private void generateSpellTree(final JPanel root, final Function<Integer, JPanel> getPanel) {		
+		for (int i = previousHighestSpellLevel; i < dclass.getHighestSpellLevel(); ++i) {
+			if (dclass.getSpellBook().get().numSpellsKnownAtLevel(i) == 0) break;
 			root.add(getPanel.apply(i));
 		}
-		
-		preparedPane.setViewportView(root);
 	}
 
 }

+ 10 - 7
src/main/lombok/org/leumasjaffe/charsheet/view/magic/SpellsPerDayHeader.java

@@ -11,6 +11,8 @@ import javax.swing.JTextField;
 import org.leumasjaffe.charsheet.model.Ability;
 import org.leumasjaffe.charsheet.model.magic.DDSpellbook;
 import org.leumasjaffe.charsheet.model.observable.IntValue;
+import org.leumasjaffe.observer.ObservableListener;
+
 import java.awt.Dimension;
 
 class SpellsPerDayHeader extends JPanel {
@@ -18,7 +20,8 @@ class SpellsPerDayHeader extends JPanel {
 	 * 
 	 */
 	private static final long serialVersionUID = 1L;
-	private JTextField textFieldRemaining;
+
+	ObservableListener<JTextField, DDSpellbook> listener;
 
 	public SpellsPerDayHeader(int level, DDSpellbook model, IntValue ability) {
 		setPreferredSize(new Dimension(350, 20));
@@ -72,7 +75,7 @@ class SpellsPerDayHeader extends JPanel {
 		gbc_lblSpellsPerDay.gridy = 0;
 		add(lblSpellsPerDay, gbc_lblSpellsPerDay);
 		
-		textFieldRemaining = new JTextField(Integer.toString(model.numSpellsPerDayRemainingAtLevel(level)));
+		JTextField textFieldRemaining = new JTextField();
 		GridBagConstraints gbc_textFieldRemaining = new GridBagConstraints();
 		gbc_textFieldRemaining.fill = GridBagConstraints.HORIZONTAL;
 		gbc_textFieldRemaining.insets = new Insets(0, 0, 0, 5);
@@ -98,10 +101,10 @@ class SpellsPerDayHeader extends JPanel {
 		add(textFieldOutOf, gbc_textFieldOutOf);
 		textFieldOutOf.setEditable(false);
 		textFieldOutOf.setColumns(10);
-	}
-
-	// Technically unsafe, but since this should only get called when a spell is cast, the number will not drop below 0
-	public void reload() {
-		textFieldRemaining.setText(Integer.toString(Integer.parseInt(textFieldRemaining.getText())-1));
+		
+		listener = new ObservableListener<JTextField, DDSpellbook>(textFieldRemaining, (c, v) -> {
+			c.setText(Integer.toString(v.numSpellsPerDayRemainingAtLevel(level)));
+		});
+		listener.setObserved(model);
 	}
 }

+ 24 - 21
src/main/lombok/org/leumasjaffe/charsheet/view/skills/SkillLevelUpLine.java

@@ -12,6 +12,7 @@ import org.leumasjaffe.observer.IndirectObservableListener;
 import org.leumasjaffe.observer.ObserverDispatch;
 
 import lombok.AccessLevel;
+import lombok.Value;
 import lombok.experimental.FieldDefaults;
 
 import java.awt.GridBagLayout;
@@ -19,6 +20,7 @@ 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;
@@ -39,7 +41,14 @@ class SkillLevelUpLine extends JPanel {
 	boolean isClassSkill;
 	DDSkill skill;
 	IntValue current;
-	IndirectObservableListener<JTextField, DDCharacter> totalListener;
+	IndirectObservableListener<JTextField, TotalPacket> totalListener;
+	
+	@Value
+	private static final class TotalPacket {
+		Optional<IntValue> ability;
+		DDSkill skill;
+		IntValue points;
+	}
 	
 	public SkillLevelUpLine(final DDCharacter chara, final DDCharacterClass cclass, final DDSkill skill, IntValue pointsAvaliable) {
 		isClassSkill = cclass.isClassSkill(skill.getName());
@@ -205,26 +214,20 @@ class SkillLevelUpLine extends JPanel {
 			if (current.value() > 0) { lambda.apply(-1); }
 		});
 
-		if ( skill.getAbility().isEmpty() ) {
-			totalListener = new IndirectObservableListener<>(total,
-					(c, v) -> {
-						c.setText(StringHelper.toString(skill.getRanks().value() + current.value()));
-					});
-			totalListener.setObserved(chara, current);
-		} else {
-			totalListener = new IndirectObservableListener<>(total,
-					(c, v) -> {
-						final int skillRanks = skill.getRanks().value();
-						final int mod = Ability.modifier(Ability.fields.get(skill.getAbility())
-								.apply(chara.getAbilities().getBase()).value());
-						c.setText(StringHelper.toString(skillRanks + mod + current.value()));
-					});
-			
-			final IntValue abilScore = 	Ability.fields.get(skill.getAbility())
-					.apply(chara.getAbilities().getBase());
-			modifier.setText(StringHelper.toString(Ability.modifier(abilScore.value())));
-			totalListener.setObserved(chara, current);
-		}
+		totalListener = new IndirectObservableListener<>(total,
+				(c, p) -> {
+					final int skillRanks = p.skill.getRanks().value();
+					final int mod = Ability.modifier(p.ability.map(v -> v.value()).orElse(10));
+					c.setText(StringHelper.toString(skillRanks + mod + p.points.value()));
+				});
+		final Optional<IntValue> ability = getAbility(chara, skill);
+		ability.ifPresent(v -> modifier.setText(StringHelper.toString(Ability.modifier(v.value()))));
+		totalListener.setObserved(new TotalPacket(ability, skill, current), current);
+	}
+
+	private Optional<IntValue> getAbility(final DDCharacter chara, final DDSkill skill) {
+		if (skill.getAbility().isEmpty()) { return Optional.empty(); }
+		else { return Optional.of(Ability.fields.get(skill.getAbility()).apply(chara.getAbilities().getBase())); }
 	}
 
 	void applyChange() {